From 9b539ca5863bac1d8fe08e3be64e8f171b9ecfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=A6=D0=BE=D0=B9?= Date: Sat, 7 Dec 2024 23:44:12 +0900 Subject: [PATCH] Bot is functional after full refactor --- bot/operations/statistics.py | 62 ++++++++++++++---- bot/utils/database.py | 29 +++++--- ...019_alter_apirequestlog_response_status.py | 19 ++++++ .../migrations/0020_alter_userhotel_user.py | 20 ++++++ hotels/models.py | 11 ++-- reports/Golden Hills 4*_report.pdf | Bin 14335 -> 14335 bytes 6 files changed, 114 insertions(+), 27 deletions(-) create mode 100644 hotels/migrations/0019_alter_apirequestlog_response_status.py create mode 100644 hotels/migrations/0020_alter_userhotel_user.py diff --git a/bot/operations/statistics.py b/bot/operations/statistics.py index 5a8453f5..e2f093e4 100644 --- a/bot/operations/statistics.py +++ b/bot/operations/statistics.py @@ -6,7 +6,8 @@ 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 +from bot.utils.database import get_hotels_for_user, get_hotel_by_name + async def statistics(update: Update, context: ContextTypes.DEFAULT_TYPE): """Вывод списка отелей для статистики.""" @@ -21,19 +22,21 @@ async def statistics(update: Update, context: ContextTypes.DEFAULT_TYPE): return # Получаем отели, связанные с пользователем - hotels = await get_hotels_for_user(user) - if not hotels: + user_hotels = await get_hotels_for_user(user) + if not user_hotels: await query.edit_message_text("У вас нет доступных отелей для статистики.") return # Формируем кнопки для выбора отеля - keyboard = [[InlineKeyboardButton(hotel.name, callback_data=f"stats_hotel_{hotel.id}")] for hotel in hotels] + 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): +async def stats_select_period(update: Update, context: ContextTypes.DEFAULT_TYPE): """Выбор периода времени для статистики.""" query = update.callback_query await query.answer() @@ -46,18 +49,21 @@ async def stats_select_period(update: Update, context): [InlineKeyboardButton("Месяц", callback_data="stats_period_month")], [InlineKeyboardButton("Все время", callback_data="stats_period_all")], [InlineKeyboardButton("🏠 Главная", callback_data="main_menu")], - [InlineKeyboardButton("🔙 Назад", callback_data="back")], + [InlineKeyboardButton("🔙 Назад", callback_data="statistics")], ] reply_markup = InlineKeyboardMarkup(keyboard) await query.edit_message_text("Выберите период времени:", reply_markup=reply_markup) - -async def generate_statistics(update: Update, context): +async def generate_statistics(update: Update, context: ContextTypes.DEFAULT_TYPE): """Генерация и отправка статистики.""" query = update.callback_query await query.answer() - hotel_id = context.user_data["selected_hotel"] + hotel_id = context.user_data.get("selected_hotel") + if not hotel_id: + await query.edit_message_text("Ошибка: ID отеля не найден.") + return + period = query.data.split("_")[2] now = datetime.now() @@ -74,8 +80,40 @@ async def generate_statistics(update: Update, context): reservations = await sync_to_async(list)( Reservation.objects.filter(hotel_id=hotel_id).prefetch_related('guests') ) + + if not reservations: + await query.edit_message_text("Нет данных для статистики за выбранный период.") + return + hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id) file_path = 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=file_path) + await query.message.reply_document(document=file, filename=f"{hotel.name}_report.pdf") + + +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) diff --git a/bot/utils/database.py b/bot/utils/database.py index 7f1409bf..9ac0ceda 100644 --- a/bot/utils/database.py +++ b/bot/utils/database.py @@ -1,21 +1,32 @@ from users.models import User -from hotels.models import Hotel, Reservation +from hotels.models import Hotel, Reservation, UserHotel from asgiref.sync import sync_to_async +from django.core.exceptions import ObjectDoesNotExist async def get_user_from_chat_id(chat_id): - return await sync_to_async(User.objects.filter(chat_id=chat_id).first)() + try: + return await sync_to_async(User.objects.get)(chat_id=chat_id) + except ObjectDoesNotExist: + return None async def get_hotel_by_id(hotel_id): - return await sync_to_async(Hotel.objects.get)(id=hotel_id) + try: + return await sync_to_async(Hotel.objects.get)(id=hotel_id) + except ObjectDoesNotExist: + return None +async def get_hotel_by_name(hotel_name): + try: + return await sync_to_async(Hotel.objects.get)(name=hotel_name) + except ObjectDoesNotExist: + return None + async def get_hotels_for_user(user): """Получение отелей, связанных с пользователем.""" - # Проверяем, является ли пользователь сотрудником какого-либо отеля - user_hotels = await sync_to_async(list)( - Hotel.objects.filter(user_hotel__user=user).distinct() + return await sync_to_async(list)( + UserHotel.objects.filter(user=user).select_related('hotel') ) - print(user_hotels) - return user_hotels + async def get_reservations(hotel_id, start_date=None, end_date=None): query = Reservation.objects.filter(hotel_id=hotel_id) @@ -23,4 +34,4 @@ async def get_reservations(hotel_id, start_date=None, end_date=None): query = query.filter(check_in__gte=start_date) if end_date: query = query.filter(check_out__lte=end_date) - return await sync_to_async(list)(query.prefetch_related('guests')) + return await sync_to_async(list)(query.prefetch_related('guests')) \ No newline at end of file diff --git a/hotels/migrations/0019_alter_apirequestlog_response_status.py b/hotels/migrations/0019_alter_apirequestlog_response_status.py new file mode 100644 index 00000000..cdb4cd10 --- /dev/null +++ b/hotels/migrations/0019_alter_apirequestlog_response_status.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2024-12-07 11:11 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hotels', '0018_alter_userhotel_hotel_alter_userhotel_user'), + ] + + operations = [ + migrations.AlterField( + model_name='apirequestlog', + name='response_status', + field=models.IntegerField(validators=[django.core.validators.MinValueValidator(100), django.core.validators.MaxValueValidator(599)], verbose_name='HTTP статус ответа'), + ), + ] diff --git a/hotels/migrations/0020_alter_userhotel_user.py b/hotels/migrations/0020_alter_userhotel_user.py new file mode 100644 index 00000000..b2a682df --- /dev/null +++ b/hotels/migrations/0020_alter_userhotel_user.py @@ -0,0 +1,20 @@ +# Generated by Django 5.1.4 on 2024-12-07 14:09 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hotels', '0019_alter_apirequestlog_response_status'), + ('users', '0005_notificationsettings'), + ] + + operations = [ + migrations.AlterField( + model_name='userhotel', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_hotels', to='users.user', verbose_name='Пользователь'), + ), + ] diff --git a/hotels/models.py b/hotels/models.py index a33a8aa3..13ddf4b7 100644 --- a/hotels/models.py +++ b/hotels/models.py @@ -1,5 +1,6 @@ from django.db import models from users.models import User +from django.core.validators import MinValueValidator, MaxValueValidator class PMSConfiguration(models.Model): @@ -74,11 +75,11 @@ class PMSIntegrationLog(models.Model): class UserHotel(models.Model): user = models.ForeignKey( - User, on_delete=models.CASCADE, related_name="user_hotel", verbose_name="Пользователь" + User, on_delete=models.CASCADE, related_name="user_hotels", verbose_name="Пользователь" ) hotel = models.ForeignKey( Hotel, on_delete=models.CASCADE, related_name="hotel_users", verbose_name="Отель" - ) +) def __str__(self): return f"{self.user.username} - {self.hotel.name}" @@ -88,12 +89,10 @@ class UserHotel(models.Model): verbose_name_plural = "Пользователи отелей" - - 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_status = models.IntegerField(verbose_name="HTTP статус ответа", validators=[MinValueValidator(100), MaxValueValidator(599)]) response_data = models.JSONField(verbose_name="Данные ответа", blank=True, null=True) def __str__(self): @@ -139,4 +138,4 @@ class Guest(models.Model): class Meta: verbose_name = "Гость" - verbose_name_plural = "Гости" + verbose_name_plural = "Гости" \ No newline at end of file diff --git a/reports/Golden Hills 4*_report.pdf b/reports/Golden Hills 4*_report.pdf index eb1d69dd22a70314f8747e9b53a402b22af1ba88..acdfa7d653b0c118c45f2747ea8c5c8b575de40f 100644 GIT binary patch delta 19 acmeyL|380&j2WAuiHWhP$z~<9yNm!*DF*`p delta 19 acmeyL|380&j2WAOsi}df*=8lPyNm!*B?kil