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 import pytz from pytz import timezone 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 from django.utils.timezone import localtime from ..utils.date_utils import ensure_datetime logger = CustomLogger(name="Statistics.py", log_level="DEBUG").get_logger() 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(f'🏨 {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_today")], [InlineKeyboardButton("Вчера", callback_data="stats_period_yesterday")], [InlineKeyboardButton("Неделя", callback_data="stats_period_week")], [InlineKeyboardButton("Этот месяц", callback_data="stats_period_thismonth")], [InlineKeyboardButton("Прошлый месяц", callback_data="stats_period_lastmonth")], [InlineKeyboardButton("Этот год", callback_data="stats_period_thisyear")], [InlineKeyboardButton("Прошлый год", callback_data="stats_period_lastyear")], [InlineKeyboardButton("🏠 Главная", callback_data="main_menu")], [InlineKeyboardButton("🔙 Назад", callback_data="statistics")], ] reply_markup = InlineKeyboardMarkup(keyboard) await query.edit_message_text("Выберите период времени:", reply_markup=reply_markup) 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("ID отеля не найден в user_data") period = query.data.split("_")[2] now = ensure_datetime(datetime.utcnow()) start_date, end_date = get_period_dates(period, now) print(type(start_date), type(end_date)) 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(f"Нет данных для статистики за выбранный период.{start_date} - {end_date}") 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: {e}", exc_info=True) await query.edit_message_text(f"Произошла ошибка: {e}") def get_period_dates(period, now=None): """ Возвращает диапазон дат (start_date, end_date) для заданного периода. :param period: Период (строка: 'today', 'yesterday', 'last_week', 'last_month'). :param now: Текущая дата/время (опционально). :return: Кортеж (start_date, end_date). :raises: ValueError, если период не поддерживается. """ if now is None: now = datetime.now(pytz.UTC) else: now = ensure_datetime(now) # Приведение now к timezone-aware if period == "today": start_date = now.replace(hour=0, minute=0, second=0, microsecond=0) end_date = now.replace(hour=23, minute=59, second=59, microsecond=999999) elif period == "yesterday": start_date = (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0) end_date = (now - timedelta(days=1)).replace(hour=23, minute=59, second=59, microsecond=999999) elif period == "week": # Последняя неделя: с понедельника предыдущей недели до воскресенья start_date = (now - timedelta(days=now.weekday() + 7)).replace(hour=0, minute=0, second=0, microsecond=0) end_date = (start_date + timedelta(days=6)).replace(hour=23, minute=59, second=59, microsecond=999999) elif period == "month": # Текущий месяц: с первого дня месяца до текущей даты start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) end_date = now.replace(hour=23, minute=59, second=59, microsecond=999999) elif period == "lastmonth": # Последний месяц: с первого дня прошлого месяца до последнего дня прошлого месяца first_day_of_current_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) last_day_of_previous_month = first_day_of_current_month - timedelta(days=1) start_date = last_day_of_previous_month.replace(day=1, hour=0, minute=0, second=0, microsecond=0) end_date = last_day_of_previous_month.replace(hour=23, minute=59, second=59, microsecond=999999) elif period == "year": # Текущий год: с первого дня года до текущей даты start_date = now.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0) end_date = now.replace(hour=23, minute=59, second=59, microsecond=999999) elif period == "lastyear": # Последний год: с 1 января предыдущего года до 31 декабря предыдущего года start_date = now.replace(year=now.year - 1, month=1, day=1, hour=0, minute=0, second=0, microsecond=0) end_date = now.replace(year=now.year - 1, month=12, day=31, hour=23, minute=59, second=59, microsecond=999999) else: raise ValueError(f"Неподдерживаемый период: {period}") # Приводим start_date и end_date к timezone-aware, если это не так if start_date.tzinfo is None: start_date = start_date.replace(tzinfo=pytz.UTC) if end_date.tzinfo is None: end_date = end_date.replace(tzinfo=pytz.UTC) # Приведение дат к локальному времени return localtime(start_date), localtime(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)