statistics and reports fixed

This commit is contained in:
2024-12-25 11:28:50 +09:00
parent 714d4834d9
commit 7861ad21b1
2 changed files with 94 additions and 187 deletions

View File

@@ -4,7 +4,8 @@ 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
@@ -13,10 +14,10 @@ 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__).get_logger()
logger = CustomLogger(name="Statistics.py", log_level="DEBUG").get_logger()
async def statistics(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Вывод списка отелей для статистики."""
@@ -57,40 +58,13 @@ async def stats_select_period(update: Update, context: ContextTypes.DEFAULT_TYPE
[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="stats_period_year")],
[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):
"""Генерация и отправка статистики."""
@@ -100,14 +74,13 @@ async def generate_statistics(update: Update, context: ContextTypes.DEFAULT_TYPE
try:
hotel_id = context.user_data.get("selected_hotel")
if not hotel_id:
raise ValueError(f"ID отеля не найден в user_data: {context.user_data}")
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,
@@ -117,7 +90,7 @@ async def generate_statistics(update: Update, context: ContextTypes.DEFAULT_TYPE
)
if not reservations:
await query.edit_message_text("Нет данных для статистики за выбранный период.")
await query.edit_message_text(f"Нет данных для статистики за выбранный период.{start_date} - {end_date}")
return
hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id)
@@ -131,30 +104,70 @@ async def generate_statistics(update: Update, context: ContextTypes.DEFAULT_TYPE
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)}")
logging.error(f"Ошибка в generate_statistics: {e}", exc_info=True)
await query.edit_message_text(f"Произошла ошибка: {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)
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:
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
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 == "last_month":
# Последний месяц: с первого дня прошлого месяца до последнего дня прошлого месяца
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 == "last_year":
# Последний год: с 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):
"""Возврат к выбору отеля."""