238 lines
10 KiB
Python
238 lines
10 KiB
Python
from datetime import datetime, timedelta, timezone
|
||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||
from telegram.ext import ContextTypes
|
||
from asgiref.sync import sync_to_async
|
||
from hotels.models import Reservation, Hotel
|
||
from users.models import User
|
||
|
||
from bot.utils.pdf_report import generate_pdf_report
|
||
from bot.utils.database import get_hotels_for_user, get_hotel_by_name
|
||
from datetime import datetime
|
||
from django.utils.timezone import make_aware, is_aware, is_naive
|
||
import os
|
||
import traceback
|
||
import logging
|
||
from touchh.utils.log import CustomLogger
|
||
|
||
|
||
|
||
|
||
async def statistics(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Вывод списка отелей для статистики."""
|
||
query = update.callback_query
|
||
user_id = query.from_user.id
|
||
await query.answer()
|
||
|
||
# Получаем пользователя
|
||
user = await sync_to_async(User.objects.filter(chat_id=user_id).first)()
|
||
if not user:
|
||
await query.edit_message_text("Вы не зарегистрированы в системе.")
|
||
return
|
||
|
||
# Получаем отели, связанные с пользователем
|
||
user_hotels = await get_hotels_for_user(user)
|
||
if not user_hotels:
|
||
await query.edit_message_text("У вас нет доступных отелей для статистики.")
|
||
return
|
||
|
||
# Формируем кнопки для выбора отеля
|
||
keyboard = [
|
||
[InlineKeyboardButton(hotel.hotel.name, callback_data=f"stats_hotel_{hotel.hotel.id}")]
|
||
for hotel in user_hotels
|
||
]
|
||
keyboard.append([InlineKeyboardButton("🏠 Главная", callback_data="main_menu")])
|
||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||
await query.edit_message_text("Выберите отель:", reply_markup=reply_markup)
|
||
|
||
async def stats_select_period(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Выбор периода времени для статистики."""
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
hotel_id = int(query.data.split("_")[2])
|
||
context.user_data["selected_hotel"] = hotel_id
|
||
|
||
keyboard = [
|
||
[InlineKeyboardButton("День", callback_data="stats_period_day")],
|
||
[InlineKeyboardButton("Неделя", callback_data="stats_period_week")],
|
||
[InlineKeyboardButton("Месяц", callback_data="stats_period_month")],
|
||
[InlineKeyboardButton("Все время", callback_data="stats_period_all")],
|
||
[InlineKeyboardButton("🏠 Главная", callback_data="main_menu")],
|
||
[InlineKeyboardButton("🔙 Назад", callback_data="statistics")],
|
||
]
|
||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||
await query.edit_message_text("Выберите период времени:", reply_markup=reply_markup)
|
||
|
||
|
||
def ensure_datetime(value):
|
||
"""
|
||
Преобразует значение в timezone-aware datetime объект, если это возможно.
|
||
|
||
:param value: Значение для преобразования
|
||
:type value: str, datetime или другое
|
||
:return: timezone-aware datetime
|
||
"""
|
||
if isinstance(value, datetime):
|
||
# Если это объект datetime, проверяем, наивен ли он
|
||
return make_aware(value) if is_naive(value) else value
|
||
elif isinstance(value, str):
|
||
# Если это строка, пытаемся преобразовать в datetime
|
||
try:
|
||
return make_aware(datetime.strptime(value, '%Y-%m-%d %H:%M:%S'))
|
||
except ValueError:
|
||
# Если формат не соответствует, пробуем более общую обработку
|
||
try:
|
||
return make_aware(datetime.fromisoformat(value))
|
||
except ValueError:
|
||
# Если не получилось распознать формат
|
||
logging.warning(f"Невозможно преобразовать строку в datetime: {value}")
|
||
else:
|
||
# Если тип неизвестен, просто возвращаем None
|
||
logging.warning(f"Получено значение неизвестного типа для преобразования в datetime: {value}")
|
||
return None
|
||
|
||
# async def generate_statistics(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
# """Генерация и отправка статистики."""
|
||
# query = update.callback_query
|
||
# await query.answer()
|
||
|
||
# try:
|
||
# hotel_id = context.user_data.get("selected_hotel")
|
||
# if not hotel_id:
|
||
# raise ValueError(f"ID отеля не найден в user_data: {context.user_data}")
|
||
|
||
# period = query.data.split("_")[2]
|
||
# now = ensure_datetime(datetime.utcnow())
|
||
|
||
# # Получаем диапазон дат
|
||
# start_date, end_date = get_period_dates(period, now)
|
||
|
||
# reservations = await sync_to_async(list)(
|
||
# Reservation.objects.filter(
|
||
# hotel_id=hotel_id,
|
||
# check_in__gte=start_date,
|
||
# check_in__lte=end_date
|
||
# ).select_related('hotel')
|
||
# )
|
||
|
||
# if not reservations:
|
||
# await query.edit_message_text("Нет данных для статистики за выбранный период.")
|
||
# return
|
||
|
||
# hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
|
||
|
||
# file_path = await generate_pdf_report(hotel.name, reservations, start_date, end_date)
|
||
|
||
# with open(file_path, "rb") as file:
|
||
# await query.message.reply_document(document=file, filename=f"{hotel.name}_report.pdf")
|
||
|
||
# if os.path.exists(file_path):
|
||
# os.remove(file_path)
|
||
|
||
# except Exception as e:
|
||
# logging.error(f"Ошибка в generate_statistics: {str(e)}", exc_info=True)
|
||
# logging.error(f'start_date_type: {type(start_date)}, \n end_date_type: {type(end_date)}\n')
|
||
# await query.edit_message_text(f"Произошла ошибка: {str(e)}")
|
||
|
||
async def generate_statistics(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||
"""Генерация и отправка статистики."""
|
||
|
||
logger = CustomLogger(__name__).get_logger()
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
try:
|
||
hotel_id = context.user_data.get("selected_hotel")
|
||
if not hotel_id:
|
||
raise ValueError(f"ID отеля не найден в user_data: {context.user_data}")
|
||
|
||
period = query.data.split("_")[2]
|
||
now = ensure_datetime(datetime.utcnow())
|
||
|
||
# Получаем диапазон дат
|
||
start_date, end_date = get_period_dates(period, now)
|
||
|
||
reservations = await sync_to_async(list)(
|
||
Reservation.objects.filter(
|
||
hotel_id=hotel_id,
|
||
check_in__gte=start_date,
|
||
check_in__lte=end_date
|
||
).select_related('hotel')
|
||
)
|
||
|
||
if not reservations:
|
||
await query.edit_message_text("Нет данных для статистики за выбранный период.")
|
||
return
|
||
|
||
hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
|
||
|
||
print(f"[DEBUG] start_date: {start_date}, type: {type(start_date)}")
|
||
print(f"[DEBUG] end_date: {end_date}, type: {type(end_date)}")
|
||
|
||
# Генерация PDF-отчета
|
||
file_path = await generate_pdf_report(
|
||
hotel.name,
|
||
reservations,
|
||
start_date=start_date.strftime('%Y-%m-%d %H:%M:%S'),
|
||
end_date=end_date.strftime('%Y-%m-%d %H:%M:%S')
|
||
)
|
||
|
||
# Отправка PDF-файла
|
||
with open(file_path, "rb") as file:
|
||
await query.message.reply_document(document=file, filename=f"{hotel.name}_report.pdf")
|
||
|
||
# Удаление временного файла
|
||
if os.path.exists(file_path):
|
||
os.remove(file_path)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в generate_statistics: {str(e)}", exc_info=True)
|
||
await query.edit_message_text(f"Произошла ошибка: {str(e)}")
|
||
|
||
|
||
def get_period_dates(period, now):
|
||
now = ensure_datetime(now)
|
||
if period == "day":
|
||
start_date = (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||
elif period == "week":
|
||
start_date = (now - timedelta(days=7)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||
elif period == "month":
|
||
start_date = (now - timedelta(days=30)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||
elif period == "all":
|
||
start_date = (now - timedelta(days=1500)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||
else:
|
||
start_date = (now - timedelta(days=1500)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||
end_date = now.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||
return start_date, end_date
|
||
|
||
|
||
async def stats_back(update: Update, context):
|
||
"""Возврат к выбору отеля."""
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
# Получаем отели, связанные с пользователем
|
||
user_id = query.from_user.id
|
||
user = await sync_to_async(User.objects.filter(chat_id=user_id).first)()
|
||
if not user:
|
||
await query.edit_message_text("Ошибка: Пользователь не найден.")
|
||
return
|
||
|
||
hotels = await get_hotels_for_user(user)
|
||
if not hotels:
|
||
await query.edit_message_text("У вас нет доступных отелей.")
|
||
return
|
||
|
||
# Формируем кнопки для выбора отеля
|
||
keyboard = [
|
||
[InlineKeyboardButton(hotel.name, callback_data=f"stats_hotel_{hotel.id}")]
|
||
for hotel in hotels
|
||
]
|
||
keyboard.append([InlineKeyboardButton("🏠 Главная", callback_data="main_menu")])
|
||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||
await query.edit_message_text("Выберите отель:", reply_markup=reply_markup)
|