from django.db import models from hotels.models import Hotel from hotels.models import Reservation from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError from geoip2.database import Reader from django.conf import settings import logging class UserActivityLog(models.Model): external_id = models.CharField(max_length=255, unique=True, verbose_name="Внешний ID", db_index=True) user_id = models.BigIntegerField(verbose_name="ID пользователя", blank=True, null=True, db_index=True) ip = models.GenericIPAddressField(verbose_name="IP-адрес", blank=True, null=True, db_index=True) created = models.DateTimeField(verbose_name="Дата создания", blank=True, null=True, db_index=True) timestamp = models.BigIntegerField(verbose_name="Метка времени", blank=True, null=True) date_time = models.DateTimeField(verbose_name="Дата и время", blank=True, null=True) referred = models.TextField(blank=True, null=True, verbose_name="Реферальная ссылка") agent = models.TextField(verbose_name="Агент пользователя", blank=True, null=True) platform = models.CharField(max_length=255, blank=True, null=True, verbose_name="Платформа") version = models.CharField(max_length=255, blank=True, null=True, verbose_name="Версия") model = models.CharField(max_length=255, blank=True, null=True, verbose_name="Модель устройства") device = models.CharField(max_length=255, blank=True, null=True, verbose_name="Тип устройства") UAString = models.TextField(verbose_name="User-Agent строка", blank=True, null=True) location = models.CharField(max_length=255, blank=True, null=True, verbose_name="Местоположение") page_id = models.BigIntegerField(blank=True, null=True, verbose_name="ID страницы", db_index=True) url_parameters = models.TextField(blank=True, null=True, verbose_name="Параметры URL") page_title = models.TextField(blank=True, null=True, verbose_name="Заголовок страницы") type = models.CharField(max_length=50, verbose_name="Тип", blank=True, null=True) last_counter = models.IntegerField(verbose_name="Последний счетчик", blank=True, null=True) hits = models.IntegerField(verbose_name="Количество обращений",default="0", blank=True, null=True) honeypot = models.BooleanField(verbose_name="Метка honeypot", blank=True, null=True) reply = models.BooleanField(verbose_name="Ответ пользователя", blank=True, null=True) page_url = models.URLField(blank=True, null=True, verbose_name="URL страницы") @property def formatted_timestamp(self): """ Преобразует Unix-временную метку в читаемую дату и время. """ if self.timestamp is not None: return datetime.fromtimestamp(self.timestamp, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S') return "Нет данных" # Изменение имени столбца class Meta: indexes = [ models.Index(fields=["external_id"], name="idx_external_id"), models.Index(fields=["user_id"], name="idx_user_id"), models.Index(fields=["ip"], name="idx_ip"), models.Index(fields=["created"], name="idx_created"), models.Index(fields=["page_id"], name="idx_page_id"), ] verbose_name = "Лог активности пользователя" verbose_name_plural = "Логи активности пользователей" def __str__(self): return f"UserActivityLog {self.id}: {self.page_title}" class Meta: verbose_name = "Регистрация посетителей" verbose_name_plural = "Регистрации посетителей" def get_location(self): if not self.ip: return "IP-адрес отсутствует" try: db_path = f"{settings.GEOIP_PATH}/GeoLite2-City.mmdb" geoip_reader = Reader(db_path) response = geoip_reader.city(self.ip) # Извлекаем город и страну на русском языке city = response.city.names.get('ru', "Город неизвестен") country = response.country.names.get('ru', "Страна неизвестна") return f"{city}, {country}" except AddressNotFoundError: return "IP-адрес не найден в базе" except FileNotFoundError: logger.error(f"Файл базы данных GeoIP не найден по пути: {db_path}") return "Файл базы данных GeoIP не найден" except Exception as e: logger.error(f"Ошибка при определении местоположения: {e}") return "Местоположение недоступно" class ExternalDBSettings(models.Model): name = models.CharField(max_length=255, unique=True, help_text="Имя подключения для идентификации.") host = models.CharField(max_length=255, help_text="Адрес сервера базы данных.") port = models.PositiveIntegerField(default=3306, help_text="Порт сервера базы данных.") user = models.CharField(max_length=255, help_text="Имя пользователя базы данных.") password = models.CharField(max_length=255, help_text="Пароль для подключения.") database = models.CharField(max_length=255, default="u1510415_wp832", help_text="Имя базы данных.") table_name = models.CharField(max_length=255, blank=True, default="wpts_user_activity_log", null=True, help_text="Имя таблицы для загрузки данных.") selected_fields = models.TextField(blank=True, null=True, help_text="Список полей для загрузки (через запятую).") is_active = models.BooleanField(default=True, help_text="Флаг активности подключения.") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"{self.name} ({self.host}:{self.port})" class Meta: verbose_name = "Настройка подключения к БД" verbose_name_plural = "Настройки подключений к БД" class RoomDiscrepancy(models.Model): hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель") room_number = models.CharField(max_length=50, verbose_name="Номер комнаты") booking_id = models.CharField(max_length=255, null=True, verbose_name="ID бронирования") check_in_date_expected = models.DateField(null=True, verbose_name="Ожидаемая дата заселения") check_in_date_actual = models.DateField(null=True, verbose_name="Фактическая дата заселения") discrepancy_type = models.CharField( max_length=50, choices=[("early", "Раннее заселение"), ("late", "Позднее заселение"), ("missed", "Неявка"), ("no_booking", "Без брони")], verbose_name="Тип несоответствия" ) created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") def __str__(self): return f"{self.hotel.name} - Room {self.room_number}: {self.discrepancy_type}" class Meta: verbose_name = "Несовпадение в заселении" verbose_name_plural = "Несовпадения в заселении" @staticmethod def detect_discrepancies(expected_bookings, actual_check_ins): """ Сравнение ожидаемых и фактических данных о заселении. """ discrepancies = [] # Преобразуем фактические заселения в словарь для быстрого доступа actual_dict = { (entry.hotel_id, entry.room_number): entry.check_in_date for entry in actual_check_ins } for booking in expected_bookings: key = (booking.hotel_id, booking.room_number) actual_date = actual_dict.get(key) if actual_date is None: discrepancies.append(RoomDiscrepancy( hotel=booking.hotel, room_number=booking.room_number, booking_id=booking.booking_id, check_in_date_expected=booking.check_in_date, discrepancy_type="missed" )) elif actual_date < booking.check_in_date: discrepancies.append(RoomDiscrepancy( hotel=booking.hotel, room_number=booking.room_number, booking_id=booking.booking_id, check_in_date_expected=booking.check_in_date, check_in_date_actual=actual_date, discrepancy_type="early" )) elif actual_date > booking.check_in_date: discrepancies.append(RoomDiscrepancy( hotel=booking.hotel, room_number=booking.room_number, booking_id=booking.booking_id, check_in_date_expected=booking.check_in_date, check_in_date_actual=actual_date, discrepancy_type="late" )) RoomDiscrepancy.objects.bulk_create(discrepancies) from urllib.parse import unquote from html import unescape class ImportedHotel(models.Model): id = models.BigAutoField(primary_key=True, auto_created=True, verbose_name="ID") external_id = models.CharField(max_length=255, verbose_name="Внешний ID отеля") name = models.CharField(max_length=255, verbose_name="Имя отеля") display_name = models.CharField(max_length=255, null=True, blank=True, verbose_name="Отображаемое имя") created = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") updated = models.DateTimeField(auto_now=True, verbose_name="Дата обновления") imported = models.BooleanField(default=False, verbose_name="Импортирован в основную базу") def __str__(self): return f"{self.display_name or self.name} ({self.external_id})" class Meta: verbose_name = "Импортированный отель" verbose_name_plural = "Импортированные отели" def set_display_name_from_page_title(self, page_title): """ Декодирует HTML-сущности, URL-кодировку и устанавливает display_name. """ if page_title: decoded = unquote(unescape(page_title)) self.display_name = decoded else: self.display_name = self.name self.save() class SyncLog(models.Model): """ Журнал синхронизации в разрезе отелей. """ hotel = models.OneToOneField(Hotel, on_delete=models.CASCADE, verbose_name="Отель") created = models.DateTimeField(auto_now=True, verbose_name="Дата обновления") # Последняя дата обновления записи recieved_records = models.IntegerField(default=0, verbose_name="Полученные записи") processed_records = models.IntegerField(default=0, verbose_name="Обработанные записи") class Meta: verbose_name = "Журнал синхронизации" verbose_name_plural = "Журналы синхронизации" def __str__(self): return f"Отель: {self.hotel.name} | Получено: {self.recieved_records} | Обработано: {self.processed_records}" class ViolationLog(models.Model): hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель") room_number = models.CharField(max_length=50, verbose_name="Номер комнаты", null=True, blank=True) violation_type = models.CharField( max_length=50, choices=[("missed", "Неявка"), ("early", "Раннее заселение"), ("late", "Позднее заселение")], verbose_name="Тип нарушения" ) violation_details = models.TextField(verbose_name="Детали нарушения", blank=True, null=True) hits = models.IntegerField(verbose_name="Срабатывания") detected_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата обнаружения") created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") def __str__(self): return f"{self.hotel.name} - {self.room_number or 'N/A'}: {self.violation_type}" class Meta: verbose_name = "Журнал нарушений" verbose_name_plural = "Журналы нарушений"