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)