Bot functionsa mainly operational
This commit is contained in:
448
bot/handlers.py
448
bot/handlers.py
@@ -1,29 +1,435 @@
|
|||||||
from telegram import Update
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||||
from telegram.ext import ContextTypes
|
from telegram.ext import ContextTypes
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from hotels.models import Hotel
|
from hotels.models import Hotel, UserHotel
|
||||||
from asgiref.sync import sync_to_async # Импортируем sync_to_async
|
from users.models import NotificationSettings
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
import smtplib
|
||||||
|
from hotels.models import PMSIntegrationLog
|
||||||
|
import requests
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# --- Вспомогательные функции ---
|
||||||
|
async def get_user_from_chat_id(chat_id):
|
||||||
|
"""Получение пользователя из базы по chat_id."""
|
||||||
|
return await sync_to_async(User.objects.filter(chat_id=chat_id).first)()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_hotel_by_id(hotel_id):
|
||||||
|
"""Получение отеля по ID."""
|
||||||
|
return await sync_to_async(Hotel.objects.filter(id=hotel_id).first)()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Обработчики команд ---
|
||||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Обработчик команды /start"""
|
"""Обработчик команды /start с проверкой chat_id"""
|
||||||
await update.message.reply_text("Привет! Я бот, работающий с Django. Используй /users или /hotels для проверки базы данных.")
|
user_id = update.message.from_user.id
|
||||||
|
print(f"Пользователь {user_id} вызвал команду /start")
|
||||||
|
|
||||||
async def list_users(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
user = await get_user_from_chat_id(user_id)
|
||||||
"""Обработчик команды /users"""
|
if user:
|
||||||
# Выполняем запрос к базе данных через sync_to_async
|
keyboard = [
|
||||||
users = await sync_to_async(list)(User.objects.all()) # Преобразуем QuerySet в список
|
[InlineKeyboardButton("📊 Статистика", callback_data="stats")],
|
||||||
if users:
|
[InlineKeyboardButton("🏨 Управление отелями", callback_data="manage_hotels")],
|
||||||
user_list = "\n".join([f"{user.id}: {user.username}" for user in users])
|
[InlineKeyboardButton("👤 Пользователи", callback_data="manage_users")],
|
||||||
await update.message.reply_text(f"Список пользователей:\n{user_list}")
|
[InlineKeyboardButton("⚙️ Настройки", callback_data="settings")],
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await update.message.reply_text("Выберите действие:", reply_markup=reply_markup)
|
||||||
else:
|
else:
|
||||||
await update.message.reply_text("В базе данных нет пользователей.")
|
print(f"Пользователь {user_id} не зарегистрирован.")
|
||||||
|
await update.message.reply_text("Вы не зарегистрированы в системе. Обратитесь к администратору.")
|
||||||
|
|
||||||
|
|
||||||
|
async def manage_hotels(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Вывод списка отелей, связанных с пользователем"""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
user_id = query.from_user.id
|
||||||
|
print(f"Пользователь {user_id} выбрал 'Управление отелями'")
|
||||||
|
|
||||||
|
user = await get_user_from_chat_id(user_id)
|
||||||
|
if not user:
|
||||||
|
print(f"Пользователь {user_id} не зарегистрирован.")
|
||||||
|
await query.edit_message_text("Вы не зарегистрированы в системе.")
|
||||||
|
return
|
||||||
|
|
||||||
|
user_hotels = await sync_to_async(list)(
|
||||||
|
UserHotel.objects.filter(user=user).select_related("hotel")
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user_hotels:
|
||||||
|
print(f"У пользователя {user_id} нет связанных отелей.")
|
||||||
|
await query.edit_message_text("У вас нет связанных отелей.")
|
||||||
|
return
|
||||||
|
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton(f"🏨 {hotel.hotel.name}", callback_data=f"hotel_{hotel.hotel.id}")]
|
||||||
|
for hotel in user_hotels
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text("Выберите отель:", reply_markup=reply_markup)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Обработчики кнопок ---
|
||||||
|
async def handle_button_click(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Обработчик всех нажатий кнопок"""
|
||||||
|
query = update.callback_query
|
||||||
|
print(f"Обработчик кнопок: Получен callback_data = {query.data}")
|
||||||
|
|
||||||
|
if query.data == "manage_hotels":
|
||||||
|
await manage_hotels(update, context)
|
||||||
|
elif query.data.startswith("hotel_"):
|
||||||
|
await hotel_actions(update, context)
|
||||||
|
elif query.data.startswith("delete_hotel_"):
|
||||||
|
await delete_hotel(update, context)
|
||||||
|
elif query.data.startswith("check_pms_"):
|
||||||
|
await check_pms(update, context)
|
||||||
|
elif query.data.startswith("setup_rooms_"):
|
||||||
|
await setup_rooms(update, context)
|
||||||
|
elif query.data == "settings":
|
||||||
|
await settings_menu(update, context)
|
||||||
|
elif query.data == "toggle_telegram":
|
||||||
|
await toggle_telegram(update, context)
|
||||||
|
elif query.data == "toggle_email":
|
||||||
|
await toggle_email(update, context)
|
||||||
|
elif query.data == "set_notification_time":
|
||||||
|
await set_notification_time(update, context)
|
||||||
|
elif query.data == "current_settings":
|
||||||
|
await show_current_settings(update, context)
|
||||||
|
|
||||||
async def list_hotels(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
||||||
"""Обработчик команды /hotels"""
|
|
||||||
# Выполняем запрос к базе данных через sync_to_async
|
|
||||||
hotels = await sync_to_async(list)(Hotel.objects.all()) # Преобразуем QuerySet в список
|
|
||||||
if hotels:
|
|
||||||
hotel_list = "\n".join([f"{hotel.id}: {hotel.name}" for hotel in hotels])
|
|
||||||
await update.message.reply_text(f"Список отелей:\n{hotel_list}")
|
|
||||||
else:
|
else:
|
||||||
await update.message.reply_text("В базе данных нет отелей.")
|
print(f"Неизвестный callback_data: {query.data}")
|
||||||
|
await query.edit_message_text("Команда не распознана.")
|
||||||
|
|
||||||
|
|
||||||
|
async def hotel_actions(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Обработчик действий для выбранного отеля"""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
hotel_id = int(query.data.split("_")[1])
|
||||||
|
print(f"Пользователь {query.from_user.id} выбрал отель с ID {hotel_id}")
|
||||||
|
|
||||||
|
hotel = await get_hotel_by_id(hotel_id)
|
||||||
|
if not hotel:
|
||||||
|
print(f"Отель с ID {hotel_id} не найден.")
|
||||||
|
await query.edit_message_text("Отель не найден.")
|
||||||
|
return
|
||||||
|
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton("🗑️ Удалить отель", callback_data=f"delete_hotel_{hotel_id}")],
|
||||||
|
[InlineKeyboardButton("🔗 Проверить интеграцию с PMS", callback_data=f"check_pms_{hotel_id}")],
|
||||||
|
[InlineKeyboardButton("🛏️ Настроить номера", callback_data=f"setup_rooms_{hotel_id}")],
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text(f"Управление отелем: {hotel.name}", reply_markup=reply_markup)
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_hotel(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Удаление отеля"""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
hotel_id = int(query.data.split("_")[2])
|
||||||
|
print(f"Пользователь {query.from_user.id} выбрал удаление отеля с ID {hotel_id}")
|
||||||
|
|
||||||
|
hotel = await get_hotel_by_id(hotel_id)
|
||||||
|
if hotel:
|
||||||
|
hotel_name = hotel.name
|
||||||
|
await sync_to_async(hotel.delete)()
|
||||||
|
print(f"Отель {hotel_name} удалён.")
|
||||||
|
await query.edit_message_text(f"Отель {hotel_name} успешно удалён.")
|
||||||
|
else:
|
||||||
|
print(f"Отель с ID {hotel_id} не найден.")
|
||||||
|
await query.edit_message_text("Отель не найден.")
|
||||||
|
|
||||||
|
|
||||||
|
async def check_pms(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Проверить интеграцию с PMS"""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
hotel_id = int(query.data.split("_")[2])
|
||||||
|
print(f"Пользователь {query.from_user.id} проверяет интеграцию PMS для отеля с ID {hotel_id}")
|
||||||
|
|
||||||
|
# Асинхронно получаем отель
|
||||||
|
hotel = await sync_to_async(Hotel.objects.select_related('api', 'pms').get)(id=hotel_id)
|
||||||
|
if not hotel:
|
||||||
|
print(f"Отель с ID {hotel_id} не найден.")
|
||||||
|
await query.edit_message_text("Отель не найден.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Асинхронно извлекаем связанные данные
|
||||||
|
api_name = hotel.api.name if hotel.api else "Не настроен"
|
||||||
|
pms_name = hotel.pms.name if hotel.pms else "Не указана"
|
||||||
|
|
||||||
|
# Формируем сообщение
|
||||||
|
status_message = (
|
||||||
|
f"Отель: {hotel.name}\n"
|
||||||
|
f"PMS система: {pms_name}\n"
|
||||||
|
f"API: {api_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
await query.edit_message_text(status_message)
|
||||||
|
async def setup_rooms(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Настроить номера отеля"""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
hotel_id = int(query.data.split("_")[2])
|
||||||
|
print(f"Пользователь {query.from_user.id} настраивает номера для отеля с ID {hotel_id}")
|
||||||
|
|
||||||
|
hotel = await get_hotel_by_id(hotel_id)
|
||||||
|
if not hotel:
|
||||||
|
print(f"Отель с ID {hotel_id} не найден.")
|
||||||
|
await query.edit_message_text("Отель не найден.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await query.edit_message_text(f"Настройка номеров для отеля: {hotel.name}")
|
||||||
|
|
||||||
|
|
||||||
|
async def settings_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Меню настроек уведомлений."""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
user_id = query.from_user.id
|
||||||
|
user = await get_user_from_chat_id(user_id)
|
||||||
|
if not user:
|
||||||
|
await query.edit_message_text("Вы не зарегистрированы.")
|
||||||
|
return
|
||||||
|
|
||||||
|
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
|
||||||
|
|
||||||
|
telegram_status = "✅" if settings.telegram_enabled else "❌"
|
||||||
|
email_status = "✅" if settings.email_enabled else "❌"
|
||||||
|
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton(f"{telegram_status} Уведомления в Telegram", callback_data="toggle_telegram")],
|
||||||
|
[InlineKeyboardButton(f"{email_status} Уведомления по Email", callback_data="toggle_email")],
|
||||||
|
[InlineKeyboardButton("🕒 Настроить время уведомлений", callback_data="set_notification_time")],
|
||||||
|
[InlineKeyboardButton("📋 Показать текущие настройки", callback_data="current_settings")],
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text("Настройки уведомлений:", reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def toggle_telegram(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Переключение состояния Telegram-уведомлений."""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
user_id = query.from_user.id
|
||||||
|
user = await get_user_from_chat_id(user_id)
|
||||||
|
if not user:
|
||||||
|
await query.edit_message_text("Вы не зарегистрированы.")
|
||||||
|
return
|
||||||
|
|
||||||
|
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
|
||||||
|
settings.telegram_enabled = not settings.telegram_enabled
|
||||||
|
await sync_to_async(settings.save)()
|
||||||
|
|
||||||
|
print(f"Пользователь {user_id} переключил Telegram-уведомления: {settings.telegram_enabled}")
|
||||||
|
await settings_menu(update, context)
|
||||||
|
|
||||||
|
|
||||||
|
async def toggle_email(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Переключение состояния Email-уведомлений."""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
user_id = query.from_user.id
|
||||||
|
user = await get_user_from_chat_id(user_id)
|
||||||
|
if not user:
|
||||||
|
await query.edit_message_text("Вы не зарегистрированы.")
|
||||||
|
return
|
||||||
|
|
||||||
|
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
|
||||||
|
settings.email_enabled = not settings.email_enabled
|
||||||
|
await sync_to_async(settings.save)()
|
||||||
|
|
||||||
|
print(f"Пользователь {user_id} переключил Email-уведомления: {settings.email_enabled}")
|
||||||
|
await settings_menu(update, context)
|
||||||
|
|
||||||
|
|
||||||
|
async def set_notification_time(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Настройка времени уведомлений."""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
user_id = query.from_user.id
|
||||||
|
user = await get_user_from_chat_id(user_id)
|
||||||
|
if not user:
|
||||||
|
await query.edit_message_text("Вы не зарегистрированы.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await query.edit_message_text("Введите новое время для уведомлений в формате HH:MM (например, 08:30):")
|
||||||
|
context.user_data["set_time"] = True
|
||||||
|
print(f"Пользователь {user_id} настраивает время уведомлений.")
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_notification_time(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Обработка ввода времени уведомлений."""
|
||||||
|
if context.user_data.get("set_time"):
|
||||||
|
user_id = update.message.from_user.id
|
||||||
|
new_time = update.message.text
|
||||||
|
|
||||||
|
try:
|
||||||
|
hour, minute = map(int, new_time.split(":"))
|
||||||
|
user = await get_user_from_chat_id(user_id)
|
||||||
|
if user:
|
||||||
|
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
|
||||||
|
settings.notification_time = f"{hour:02}:{minute:02}"
|
||||||
|
await sync_to_async(settings.save)()
|
||||||
|
print(f"Пользователь {user_id} установил новое время уведомлений: {new_time}")
|
||||||
|
await update.message.reply_text(f"Время уведомлений обновлено на {new_time}.")
|
||||||
|
except ValueError:
|
||||||
|
print(f"Пользователь {user_id} ввёл некорректное время: {new_time}")
|
||||||
|
await update.message.reply_text("Неверный формат. Введите время в формате HH:MM.")
|
||||||
|
finally:
|
||||||
|
context.user_data["set_time"] = False
|
||||||
|
|
||||||
|
|
||||||
|
async def settings_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Меню настроек уведомлений."""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
user_id = query.from_user.id
|
||||||
|
user = await get_user_from_chat_id(user_id)
|
||||||
|
if not user:
|
||||||
|
await query.edit_message_text("Вы не зарегистрированы.")
|
||||||
|
return
|
||||||
|
|
||||||
|
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
|
||||||
|
|
||||||
|
telegram_status = "✅" if settings.telegram_enabled else "❌"
|
||||||
|
email_status = "✅" if settings.email_enabled else "❌"
|
||||||
|
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton(f"{telegram_status} Уведомления в Telegram", callback_data="toggle_telegram")],
|
||||||
|
[InlineKeyboardButton(f"{email_status} Уведомления по Email", callback_data="toggle_email")],
|
||||||
|
[InlineKeyboardButton("🕒 Настроить время уведомлений", callback_data="set_notification_time")],
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text("Настройки уведомлений:", reply_markup=reply_markup)
|
||||||
|
|
||||||
|
async def send_telegram_notification(user, message):
|
||||||
|
"""Отправка уведомления через Telegram."""
|
||||||
|
|
||||||
|
# bot = Bot(token="ВАШ_ТОКЕН")
|
||||||
|
if user.chat_id:
|
||||||
|
try:
|
||||||
|
await bot.send_message(chat_id=user.chat_id, text=message)
|
||||||
|
print(f"Telegram-уведомление отправлено пользователю {user.chat_id}: {message}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка отправки Telegram-уведомления пользователю {user.chat_id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def send_email_notification(user, message):
|
||||||
|
"""Отправка уведомления через Email."""
|
||||||
|
if user.email:
|
||||||
|
try:
|
||||||
|
send_mail(
|
||||||
|
subject="Уведомление от системы",
|
||||||
|
message=message,
|
||||||
|
from_email="noreply@yourdomain.com",
|
||||||
|
recipient_list=[user.email],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
print(f"Email-уведомление отправлено на {user.email}: {message}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка отправки Email-уведомления пользователю {user.email}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
async def schedule_notifications():
|
||||||
|
"""Планировщик уведомлений."""
|
||||||
|
print("Запуск планировщика уведомлений...")
|
||||||
|
now = datetime.now().strftime("%H:%M")
|
||||||
|
|
||||||
|
users = await sync_to_async(list)(User.objects.all())
|
||||||
|
for user in users:
|
||||||
|
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
|
||||||
|
|
||||||
|
if settings.notification_time == now:
|
||||||
|
message = "Это ваше уведомление от системы."
|
||||||
|
if settings.telegram_enabled:
|
||||||
|
await send_telegram_notification(user, message)
|
||||||
|
if settings.email_enabled:
|
||||||
|
send_email_notification(user, message)
|
||||||
|
|
||||||
|
|
||||||
|
async def show_current_settings(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Отображение текущих настроек уведомлений."""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
user_id = query.from_user.id
|
||||||
|
user = await get_user_from_chat_id(user_id)
|
||||||
|
if not user:
|
||||||
|
await query.edit_message_text("Вы не зарегистрированы.")
|
||||||
|
return
|
||||||
|
|
||||||
|
settings, _ = await sync_to_async(NotificationSettings.objects.get_or_create)(user=user)
|
||||||
|
telegram_status = "✅ Включены" if settings.telegram_enabled else "❌ Выключены"
|
||||||
|
email_status = "✅ Включены" if settings.email_enabled else "❌ Выключены"
|
||||||
|
notification_time = settings.notification_time or "Не установлено"
|
||||||
|
|
||||||
|
message = (
|
||||||
|
f"📋 Ваши настройки уведомлений:\n"
|
||||||
|
f"🔔 Telegram: {telegram_status}\n"
|
||||||
|
f"📧 Email: {email_status}\n"
|
||||||
|
f"🕒 Время: {notification_time}"
|
||||||
|
)
|
||||||
|
await query.edit_message_text(message)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def check_pms_integration(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
hotel_id = int(query.data.split("_")[2])
|
||||||
|
hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
|
||||||
|
pms_settings = hotel.pms.parser_settings # Настройки из связанной PMSConfiguration
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Выполняем запрос к PMS
|
||||||
|
response = requests.post(
|
||||||
|
url=pms_settings["url"],
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {hotel.api.api_key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
json={
|
||||||
|
"from": "2024-01-01T00:00:00Z",
|
||||||
|
"until": "2024-01-10T00:00:00Z",
|
||||||
|
"pagination": {"from": 0, "count": 10},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Проверяем результат
|
||||||
|
if response.status_code == 200:
|
||||||
|
await sync_to_async(PMSIntegrationLog.objects.create)(
|
||||||
|
hotel=hotel,
|
||||||
|
status="success",
|
||||||
|
message="Интеграция успешно проверена.",
|
||||||
|
)
|
||||||
|
await query.edit_message_text(f"Интеграция с PMS для отеля '{hotel.name}' успешна.")
|
||||||
|
else:
|
||||||
|
await sync_to_async(PMSIntegrationLog.objects.create)(
|
||||||
|
hotel=hotel,
|
||||||
|
status="error",
|
||||||
|
message=f"Ошибка: {response.status_code}",
|
||||||
|
)
|
||||||
|
await query.edit_message_text(f"Ошибка интеграции с PMS для отеля '{hotel.name}': {response.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
await sync_to_async(PMSIntegrationLog.objects.create)(
|
||||||
|
hotel=hotel,
|
||||||
|
status="error",
|
||||||
|
message=str(e),
|
||||||
|
)
|
||||||
|
await query.edit_message_text(f"Произошла ошибка: {str(e)}")
|
||||||
|
|||||||
@@ -1,28 +1,97 @@
|
|||||||
import os
|
import os
|
||||||
import django
|
import django
|
||||||
|
import asyncio
|
||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from telegram.ext import Application, CommandHandler
|
from telegram.ext import (
|
||||||
from bot.handlers import start, list_users, list_hotels # Импорт обработчиков
|
Application,
|
||||||
|
CommandHandler,
|
||||||
|
CallbackQueryHandler,
|
||||||
|
MessageHandler,
|
||||||
|
filters,
|
||||||
|
)
|
||||||
|
from bot.handlers import (
|
||||||
|
start,
|
||||||
|
handle_button_click,
|
||||||
|
manage_hotels,
|
||||||
|
hotel_actions,
|
||||||
|
delete_hotel,
|
||||||
|
check_pms,
|
||||||
|
setup_rooms,
|
||||||
|
settings_menu,
|
||||||
|
toggle_telegram,
|
||||||
|
toggle_email,
|
||||||
|
set_notification_time,
|
||||||
|
handle_notification_time,
|
||||||
|
schedule_notifications,
|
||||||
|
show_current_settings,
|
||||||
|
)
|
||||||
|
|
||||||
# Настройка Django окружения
|
# Настройка Django окружения
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'touchh.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'touchh.settings')
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
def main():
|
|
||||||
# Создаём приложение Telegram
|
|
||||||
application = Application.builder().token("8125171867:AAGxDcSpQxJy3_pmq3TDBWtqaAVCj7b-F5k").build()
|
|
||||||
|
|
||||||
# Регистрируем обработчики команд
|
async def start_bot(application):
|
||||||
|
"""Настройка и запуск Telegram бота."""
|
||||||
|
print("Настройка Telegram приложения...")
|
||||||
|
|
||||||
|
# Регистрация обработчиков команд
|
||||||
|
print("Регистрация обработчиков команд...")
|
||||||
application.add_handler(CommandHandler("start", start))
|
application.add_handler(CommandHandler("start", start))
|
||||||
application.add_handler(CommandHandler("users", list_users))
|
|
||||||
application.add_handler(CommandHandler("hotels", list_hotels))
|
|
||||||
|
|
||||||
# Запускаем бота
|
# Регистрация обработчиков кнопок
|
||||||
application.run_polling()
|
print("Регистрация обработчиков кнопок...")
|
||||||
|
application.add_handler(CallbackQueryHandler(handle_button_click))
|
||||||
|
application.add_handler(CallbackQueryHandler(manage_hotels, pattern="^manage_hotels$"))
|
||||||
|
application.add_handler(CallbackQueryHandler(settings_menu, pattern="^settings$"))
|
||||||
|
application.add_handler(CallbackQueryHandler(toggle_telegram, pattern="^toggle_telegram$"))
|
||||||
|
application.add_handler(CallbackQueryHandler(toggle_email, pattern="^toggle_email$"))
|
||||||
|
application.add_handler(CallbackQueryHandler(set_notification_time, pattern="^set_notification_time$"))
|
||||||
|
application.add_handler(CallbackQueryHandler(show_current_settings, pattern="^current_settings$"))
|
||||||
|
application.add_handler(CallbackQueryHandler(hotel_actions, pattern="^hotel_"))
|
||||||
|
application.add_handler(CallbackQueryHandler(delete_hotel, pattern="^delete_hotel_"))
|
||||||
|
application.add_handler(CallbackQueryHandler(check_pms, pattern="^check_pms_"))
|
||||||
|
application.add_handler(CallbackQueryHandler(setup_rooms, pattern="^setup_rooms_"))
|
||||||
|
|
||||||
|
# Регистрация обработчиков текстовых сообщений
|
||||||
|
print("Регистрация обработчиков текстовых сообщений...")
|
||||||
|
application.add_handler(MessageHandler(filters.TEXT & filters.ChatType.PRIVATE, handle_notification_time))
|
||||||
|
|
||||||
|
# Настройка планировщика
|
||||||
|
print("Настройка планировщика уведомлений...")
|
||||||
|
scheduler = AsyncIOScheduler()
|
||||||
|
scheduler.add_job(schedule_notifications, "cron", minute="*")
|
||||||
|
scheduler.start()
|
||||||
|
|
||||||
|
# Запуск бота
|
||||||
|
print("Запуск Telegram бота...")
|
||||||
|
await application.initialize()
|
||||||
|
await application.start()
|
||||||
|
print("Бот успешно запущен. Ожидание событий...")
|
||||||
|
await application.updater.start_polling()
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Запуск Telegram бота"
|
help = "Запуск Telegram бота"
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
self.stdout.write("Запуск Telegram бота...")
|
print("Запуск Telegram бота...")
|
||||||
main()
|
|
||||||
|
# Получаем текущий цикл событий
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
# Создаём экземпляр приложения
|
||||||
|
application = Application.builder().token("8125171867:AAGxDcSpQxJy3_pmq3TDBWtqaAVCj7b-F5k").build()
|
||||||
|
|
||||||
|
# Добавляем задачу для запуска бота
|
||||||
|
loop.create_task(start_bot(application))
|
||||||
|
|
||||||
|
# Запускаем цикл событий
|
||||||
|
try:
|
||||||
|
loop.run_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Остановка Telegram бота...")
|
||||||
|
loop.run_until_complete(application.stop())
|
||||||
|
scheduler = AsyncIOScheduler()
|
||||||
|
scheduler.shutdown(wait=False)
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Hotel, UserHotel
|
from .models import Hotel, UserHotel, APIConfiguration, APIRequestLog, PMSConfiguration, PMSIntegrationLog
|
||||||
|
from django import forms
|
||||||
|
class HotelForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Hotel
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
# Исключаем API, которые уже связаны с другими отелями
|
||||||
|
used_apis = Hotel.objects.exclude(api__isnull=True).values_list('api', flat=True)
|
||||||
|
self.fields['api'].queryset = APIConfiguration.objects.exclude(id__in=used_apis)
|
||||||
|
|
||||||
@admin.register(Hotel)
|
|
||||||
class HotelAdmin(admin.ModelAdmin):
|
class HotelAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'pms_type', 'created_at')
|
form = HotelForm
|
||||||
|
list_display = ('name', 'api', 'created_at', 'pms')
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
list_filter = ('pms_type',)
|
|
||||||
ordering = ('-created_at',)
|
admin.site.register(Hotel, HotelAdmin)
|
||||||
|
|
||||||
@admin.register(UserHotel)
|
@admin.register(UserHotel)
|
||||||
class UserHotelAdmin(admin.ModelAdmin):
|
class UserHotelAdmin(admin.ModelAdmin):
|
||||||
@@ -15,3 +26,31 @@ class UserHotelAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ('hotel',)
|
list_filter = ('hotel',)
|
||||||
ordering = ('-hotel',)
|
ordering = ('-hotel',)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(APIConfiguration)
|
||||||
|
class ApiConfigurationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'url', 'token', 'username', 'password')
|
||||||
|
search_fields = ('name', 'url', 'token', 'username', 'password')
|
||||||
|
list_filter = ('name', 'url', 'token', 'username', 'password')
|
||||||
|
ordering = ('-name',)
|
||||||
|
|
||||||
|
@admin.register(APIRequestLog)
|
||||||
|
class ApiRequestLogAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('api', 'request_time', 'response_status', 'response_data')
|
||||||
|
search_fields = ('api', 'request_time', 'response_status', 'response_data')
|
||||||
|
list_filter = ('api', 'request_time', 'response_status', 'response_data')
|
||||||
|
ordering = ('-api',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(PMSConfiguration)
|
||||||
|
class PMSConfigurationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'parser_settings', 'description')
|
||||||
|
|
||||||
|
@admin.register(PMSIntegrationLog)
|
||||||
|
class PMSIntegreationLogAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('hotel', 'checked_at', 'status', 'message')
|
||||||
|
search_fields = ('hotel', 'checked_at', 'status', 'message')
|
||||||
|
list_filter = ('hotel', 'checked_at', 'status', 'message')
|
||||||
|
ordering = ('-checked_at',)
|
||||||
|
|
||||||
26
hotels/migrations/0004_datalog.py
Normal file
26
hotels/migrations/0004_datalog.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-06 13:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hotels', '0003_alter_hotel_options_alter_userhotel_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DataLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Название')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||||
|
('data', models.JSONField(verbose_name='Данные')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Лог данных',
|
||||||
|
'verbose_name_plural': 'Логи данных',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-06 13:51
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hotels', '0004_datalog'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='APIConfiguration',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Название API')),
|
||||||
|
('url', models.URLField(verbose_name='URL API')),
|
||||||
|
('token', models.CharField(blank=True, max_length=255, null=True, verbose_name='Токен')),
|
||||||
|
('username', models.CharField(blank=True, max_length=255, null=True, verbose_name='Логин')),
|
||||||
|
('password', models.CharField(blank=True, max_length=255, null=True, verbose_name='Пароль')),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, verbose_name='Дата последнего обновления')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Конфигурация API',
|
||||||
|
'verbose_name_plural': 'Конфигурации API',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='APIRequestLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('request_time', models.DateTimeField(auto_now_add=True, verbose_name='Время запроса')),
|
||||||
|
('response_status', models.IntegerField(verbose_name='HTTP статус ответа')),
|
||||||
|
('response_data', models.JSONField(blank=True, null=True, verbose_name='Данные ответа')),
|
||||||
|
('api', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.apiconfiguration', verbose_name='API')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Журнал запросов API',
|
||||||
|
'verbose_name_plural': 'Журналы запросов API',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='DataLog',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-06 14:09
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hotels', '0005_apiconfiguration_apirequestlog_delete_datalog'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PMSConfiguration',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, verbose_name='Название PMS')),
|
||||||
|
('parser_settings', models.JSONField(default=dict, verbose_name='Настройки разбора данных')),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'PMS система',
|
||||||
|
'verbose_name_plural': 'PMS системы',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='hotel',
|
||||||
|
name='api_key',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='hotel',
|
||||||
|
name='pms_type',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='hotel',
|
||||||
|
name='public_key',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='hotel',
|
||||||
|
name='api',
|
||||||
|
field=models.OneToOneField(blank=True, help_text='API, связанный с этим отелем.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.apiconfiguration', verbose_name='API'),
|
||||||
|
),
|
||||||
|
]
|
||||||
28
hotels/migrations/0007_pmsintegrationlog.py
Normal file
28
hotels/migrations/0007_pmsintegrationlog.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-06 23:02
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hotels', '0006_pmsconfiguration_remove_hotel_api_key_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PMSIntegrationLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('checked_at', models.DateTimeField(auto_now_add=True, verbose_name='Время проверки')),
|
||||||
|
('status', models.CharField(choices=[('success', 'Успех'), ('error', 'Ошибка')], max_length=50, verbose_name='Статус')),
|
||||||
|
('message', models.TextField(blank=True, null=True, verbose_name='Сообщение')),
|
||||||
|
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Журнал интеграции PMS',
|
||||||
|
'verbose_name_plural': 'Журналы интеграции PMS',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
19
hotels/migrations/0008_hotel_pms.py
Normal file
19
hotels/migrations/0008_hotel_pms.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-06 23:38
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hotels', '0007_pmsintegrationlog'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='hotel',
|
||||||
|
name='pms',
|
||||||
|
field=models.OneToOneField(blank=True, help_text='PMS система? используемая в заведении', null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.pmsconfiguration', verbose_name='PMS система'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
hotels/migrations/0009_alter_hotel_pms.py
Normal file
19
hotels/migrations/0009_alter_hotel_pms.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-06 23:57
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hotels', '0008_hotel_pms'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hotel',
|
||||||
|
name='pms',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.pmsconfiguration', verbose_name='PMS система'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-07 00:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hotels', '0009_alter_hotel_pms'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='apirequestlog',
|
||||||
|
index=models.Index(fields=['api'], name='hotels_apir_api_id_686bb0_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='apirequestlog',
|
||||||
|
index=models.Index(fields=['request_time'], name='hotels_apir_request_f65147_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='pmsintegrationlog',
|
||||||
|
index=models.Index(fields=['hotel'], name='hotels_pmsi_hotel_i_718b32_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='pmsintegrationlog',
|
||||||
|
index=models.Index(fields=['checked_at'], name='hotels_pmsi_checked_e7768d_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='pmsintegrationlog',
|
||||||
|
index=models.Index(fields=['status'], name='hotels_pmsi_status_dbf1d8_idx'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,16 +1,48 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class PMSConfiguration(models.Model):
|
||||||
|
name = models.CharField(max_length=255, verbose_name="Название PMS")
|
||||||
|
parser_settings = models.JSONField(default=dict, verbose_name="Настройки разбора данных")
|
||||||
|
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "PMS система"
|
||||||
|
verbose_name_plural = "PMS системы"
|
||||||
|
|
||||||
|
|
||||||
|
class APIConfiguration(models.Model):
|
||||||
|
name = models.CharField(max_length=255, verbose_name="Название API")
|
||||||
|
url = models.URLField(verbose_name="URL API")
|
||||||
|
token = models.CharField(max_length=255, blank=True, null=True, verbose_name="Токен")
|
||||||
|
username = models.CharField(max_length=255, blank=True, null=True, verbose_name="Логин")
|
||||||
|
password = models.CharField(max_length=255, blank=True, null=True, verbose_name="Пароль")
|
||||||
|
last_updated = models.DateTimeField(auto_now=True, verbose_name="Дата последнего обновления")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Конфигурация API"
|
||||||
|
verbose_name_plural = "Конфигурации API"
|
||||||
|
|
||||||
class Hotel(models.Model):
|
class Hotel(models.Model):
|
||||||
name = models.CharField(max_length=255, verbose_name="Название отеля")
|
name = models.CharField(max_length=255, verbose_name="Название отеля")
|
||||||
pms_type = models.CharField(
|
api = models.OneToOneField(
|
||||||
max_length=50,
|
APIConfiguration,
|
||||||
choices=[('bnovo', 'Bnovo'), ('travelline', 'Travel Line')],
|
on_delete=models.SET_NULL,
|
||||||
verbose_name="PMS система"
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="API",
|
||||||
|
help_text="API, связанный с этим отелем."
|
||||||
)
|
)
|
||||||
api_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="API ключ")
|
|
||||||
public_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="Публичный ключ")
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
|
||||||
|
pms = models.ForeignKey(PMSConfiguration, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="PMS система")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -19,6 +51,21 @@ class Hotel(models.Model):
|
|||||||
verbose_name = "Отель"
|
verbose_name = "Отель"
|
||||||
verbose_name_plural = "Отели"
|
verbose_name_plural = "Отели"
|
||||||
|
|
||||||
|
class PMSIntegrationLog(models.Model):
|
||||||
|
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель")
|
||||||
|
checked_at = models.DateTimeField(auto_now_add=True, verbose_name="Время проверки")
|
||||||
|
status = models.CharField(max_length=50, verbose_name="Статус", choices=[('success', 'Успех'), ('error', 'Ошибка')])
|
||||||
|
message = models.TextField(verbose_name="Сообщение", blank=True, null=True)
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.hotel.name} - {self.status} - {self.checked_at}"
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Журнал интеграции PMS"
|
||||||
|
verbose_name_plural = "Журналы интеграции PMS"
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['hotel']),
|
||||||
|
models.Index(fields=['checked_at']),
|
||||||
|
models.Index(fields=['status']),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserHotel(models.Model):
|
class UserHotel(models.Model):
|
||||||
@@ -32,3 +79,25 @@ class UserHotel(models.Model):
|
|||||||
verbose_name = "Пользователь отеля"
|
verbose_name = "Пользователь отеля"
|
||||||
verbose_name_plural = "Пользователи отелей"
|
verbose_name_plural = "Пользователи отелей"
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class APIRequestLog(models.Model):
|
||||||
|
api = models.ForeignKey(APIConfiguration, on_delete=models.CASCADE, verbose_name="API")
|
||||||
|
request_time = models.DateTimeField(auto_now_add=True, verbose_name="Время запроса")
|
||||||
|
response_status = models.IntegerField(verbose_name="HTTP статус ответа")
|
||||||
|
response_data = models.JSONField(verbose_name="Данные ответа", blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.api.name} - {self.request_time}"
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Журнал запросов API"
|
||||||
|
verbose_name_plural = "Журналы запросов API"
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['api']),
|
||||||
|
models.Index(fields=['request_time']),
|
||||||
|
]
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|||||||
92
hotels/pms_check.py
Normal file
92
hotels/pms_check.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import requests
|
||||||
|
from django.db import models
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
|
||||||
|
|
||||||
|
class APIDataLogger:
|
||||||
|
"""Класс для работы с API, сохранения и обработки данных."""
|
||||||
|
|
||||||
|
def __init__(self, name, url, token=None, username=None, password=None):
|
||||||
|
self.name = name
|
||||||
|
self.url = url
|
||||||
|
self.token = token
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.output_dir = "modules"
|
||||||
|
|
||||||
|
def ensure_directory_exists(self, path):
|
||||||
|
"""Создать директорию, если она не существует."""
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.makedirs(path)
|
||||||
|
|
||||||
|
def fetch_data(self, additional_data=None):
|
||||||
|
"""Получить данные из API."""
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
data = additional_data or {}
|
||||||
|
|
||||||
|
if self.token:
|
||||||
|
data["token"] = self.token
|
||||||
|
|
||||||
|
response = requests.post(self.url, headers=headers, json=data, auth=(self.username, self.password) if self.username and self.password else None)
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f'{self.name}: API запрос не удался. Код статуса: {response.status_code}')
|
||||||
|
return []
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def save_data(self, data, suffix):
|
||||||
|
"""Сохранить данные в файл JSON."""
|
||||||
|
now = datetime.now()
|
||||||
|
current_date = now.strftime('%Y-%m-%d')
|
||||||
|
directory = os.path.join(self.output_dir, current_date, self.name)
|
||||||
|
self.ensure_directory_exists(directory)
|
||||||
|
|
||||||
|
filename = f"{self.name} {suffix}.json"
|
||||||
|
filepath = os.path.join(directory, filename)
|
||||||
|
with open(filepath, 'w') as file:
|
||||||
|
json.dump(data, file)
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
def load_previous_data(self, suffixes):
|
||||||
|
"""Загрузить данные из файлов за текущий и предыдущий интервалы."""
|
||||||
|
now = datetime.now()
|
||||||
|
current_date = now.strftime('%Y-%m-%d')
|
||||||
|
yesterday_date = (now - timedelta(days=1)).strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
directories = [(yesterday_date, "21"), (current_date, "9")] if 9 <= now.hour < 21 else [(current_date, "9"), (current_date, "21")]
|
||||||
|
data_combined = []
|
||||||
|
|
||||||
|
for date, suffix in directories:
|
||||||
|
filepath = os.path.join(self.output_dir, date, self.name, f"{self.name} {suffix}.json")
|
||||||
|
try:
|
||||||
|
with open(filepath, 'r') as file:
|
||||||
|
data_combined.extend(json.load(file))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return data_combined
|
||||||
|
|
||||||
|
def filter_data(self, data, filter_function):
|
||||||
|
"""Фильтрация данных с использованием переданной функции."""
|
||||||
|
return filter_function(data)
|
||||||
|
|
||||||
|
def process_and_save(self, additional_data=None, filter_function=None):
|
||||||
|
"""Основной процесс: запрос, сохранение, чтение, фильтрация."""
|
||||||
|
now = datetime.now()
|
||||||
|
suffix = "9" if 9 <= now.hour < 21 else "21"
|
||||||
|
|
||||||
|
# Шаг 1: Получить данные
|
||||||
|
raw_data = self.fetch_data(additional_data)
|
||||||
|
self.save_data(raw_data, suffix)
|
||||||
|
|
||||||
|
# Шаг 2: Загрузить данные за текущий и предыдущий интервал
|
||||||
|
combined_data = self.load_previous_data(["9", "21"])
|
||||||
|
|
||||||
|
# Шаг 3: Фильтрация
|
||||||
|
if filter_function:
|
||||||
|
combined_data = self.filter_data(combined_data, filter_function)
|
||||||
|
|
||||||
|
return combined_data
|
||||||
53
hotels/pms_parse.py
Normal file
53
hotels/pms_parse.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def parse_pms_data(data, parser_settings):
|
||||||
|
date_format = parser_settings["date_format"]
|
||||||
|
fields_mapping = parser_settings["fields_mapping"]
|
||||||
|
conditions = parser_settings.get("conditions", {})
|
||||||
|
|
||||||
|
parsed_data = []
|
||||||
|
|
||||||
|
for record in data:
|
||||||
|
# Применение условий фильтрации
|
||||||
|
if conditions:
|
||||||
|
for condition_field, expected_value in conditions.items():
|
||||||
|
if record.get(condition_field) != expected_value:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Условие выполнено
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Условие отсутствует
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Разбор полей
|
||||||
|
parsed_record = {}
|
||||||
|
for internal_field, external_field in fields_mapping.items():
|
||||||
|
if "." in external_field: # Например, "guests[0].lastName"
|
||||||
|
keys = external_field.split(".")
|
||||||
|
value = record
|
||||||
|
try:
|
||||||
|
for key in keys:
|
||||||
|
if key.endswith("]"): # Обработка индексов, например, "guests[0]"
|
||||||
|
key, index = key[:-1].split("[")
|
||||||
|
value = value[key][int(index)]
|
||||||
|
else:
|
||||||
|
value = value[key]
|
||||||
|
except (KeyError, IndexError, TypeError):
|
||||||
|
value = None
|
||||||
|
else:
|
||||||
|
value = record.get(external_field)
|
||||||
|
|
||||||
|
# Преобразование дат
|
||||||
|
if "date" in internal_field or "time" in internal_field:
|
||||||
|
try:
|
||||||
|
value = datetime.strptime(value, date_format)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
value = None
|
||||||
|
|
||||||
|
parsed_record[internal_field] = value
|
||||||
|
|
||||||
|
parsed_data.append(parsed_record)
|
||||||
|
|
||||||
|
return parsed_data
|
||||||
@@ -87,17 +87,10 @@ DATABASES = {
|
|||||||
'wordpress': {
|
'wordpress': {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'NAME': 'u1510415_wp832',
|
'NAME': 'u1510415_wp832',
|
||||||
<<<<<<< HEAD
|
|
||||||
'USER': 'root',
|
|
||||||
'PASSWORD': 'R0sebud',
|
|
||||||
'HOST': '0.0.0.0',
|
|
||||||
'PORT': '3308',
|
|
||||||
=======
|
|
||||||
'USER': 'u1510415_wp832',
|
'USER': 'u1510415_wp832',
|
||||||
'PASSWORD': 'yZ1gV6kH6lzD2cQ3',
|
'PASSWORD': 'yZ1gV6kH6lzD2cQ3',
|
||||||
'HOST': 'server231.hosting.reg.ru',
|
'HOST': 'server231.hosting.reg.ru',
|
||||||
'PORT': '3306',
|
'PORT': '3306',
|
||||||
>>>>>>> cea14d40d73e3a67c30dda8765458d91652e6bf4
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
# Password validation
|
# Password validation
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import User, UserConfirmation, UserActivityLog
|
from .models import User, UserConfirmation, UserActivityLog, NotificationSettings
|
||||||
|
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
class UserAdmin(admin.ModelAdmin):
|
class UserAdmin(admin.ModelAdmin):
|
||||||
@@ -21,3 +21,10 @@ class UserActivityLogAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ('page_title', 'user_id', 'ip')
|
list_filter = ('page_title', 'user_id', 'ip')
|
||||||
ordering = ('-id',)
|
ordering = ('-id',)
|
||||||
|
|
||||||
|
@admin.register(NotificationSettings)
|
||||||
|
class NotificationSettingsAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('user', 'email', 'telegram_enabled', 'email_enabled', 'notification_time')
|
||||||
|
search_fields = ('user', 'email', 'telegram_enabled', 'email_enabled', 'notification_time')
|
||||||
|
list_filter = ('user', 'email', 'telegram_enabled', 'email_enabled', 'notification_time')
|
||||||
|
ordering = ('-id',)
|
||||||
|
|
||||||
29
users/migrations/0005_notificationsettings.py
Normal file
29
users/migrations/0005_notificationsettings.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-06 12:01
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0004_alter_user_options_alter_userconfirmation_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NotificationSettings',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('telegram_enabled', models.BooleanField(default=True, verbose_name='Уведомления в Telegram')),
|
||||||
|
('email_enabled', models.BooleanField(default=False, verbose_name='Уведомления по Email')),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email для уведомлений')),
|
||||||
|
('notification_time', models.TimeField(default='09:00', verbose_name='Время отправки уведомлений')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='Пользователь')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Способ оповещения',
|
||||||
|
'verbose_name_plural': 'Способы оповещений',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -113,3 +113,17 @@ class UserActivityLog(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"User {self.user_id} - {self.type} - {self.date_time}"
|
return f"User {self.user_id} - {self.type} - {self.date_time}"
|
||||||
|
|
||||||
|
class NotificationSettings(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name="Пользователь")
|
||||||
|
telegram_enabled = models.BooleanField(default=True, verbose_name="Уведомления в Telegram")
|
||||||
|
email_enabled = models.BooleanField(default=False, verbose_name="Уведомления по Email")
|
||||||
|
email = models.EmailField(blank=True, null=True, verbose_name="Email для уведомлений")
|
||||||
|
notification_time = models.TimeField(default="09:00", verbose_name="Время отправки уведомлений")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Настройки уведомлений для {self.user.username}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Способ оповещения"
|
||||||
|
verbose_name_plural = "Способы оповещений"
|
||||||
Reference in New Issue
Block a user