diff --git a/antifroud/admin.py b/antifroud/admin.py index d91c877c..c59ee693 100644 --- a/antifroud/admin.py +++ b/antifroud/admin.py @@ -151,6 +151,7 @@ class UserActivityLogAdmin(admin.ModelAdmin): get_hotel_name.short_description = "Отель" get_room_number.short_description = "Комната" + # from .views import import_selected_hotels # # Регистрируем admin класс для ImportedHotel # @admin.register(ImportedHotel) @@ -247,4 +248,3 @@ class RoomDiscrepancyAdmin(admin.ModelAdmin): class Meta: model = RoomDiscrepancy fields = ['hotel', 'room_number', 'booking_id','created_at', 'check_in_date_expected','check_in_date_actual','discrepancy_type'] - \ No newline at end of file diff --git a/antifroud/data_sync.py b/antifroud/data_sync.py index a987c117..4f5f3dcc 100644 --- a/antifroud/data_sync.py +++ b/antifroud/data_sync.py @@ -314,7 +314,7 @@ def scheduled_sync(): except Exception as e: logger.error(f"Error syncing connection {db_settings}: {e}") - with ThreadPoolExecutor(max_workers=5) as executor: + with ThreadPoolExecutor(max_workers=10) as executor: futures = [executor.submit(sync_task, db_settings) for db_settings in active_db_settings] for future in futures: try: diff --git a/antifroud/models.py b/antifroud/models.py index 0f7a861e..d113a827 100644 --- a/antifroud/models.py +++ b/antifroud/models.py @@ -78,10 +78,10 @@ class UserActivityLog(models.Model): except AddressNotFoundError: return "IP-адрес не найден в базе" except FileNotFoundError: - logger.error(f"Файл базы данных GeoIP не найден по пути: {db_path}") + # logger.error(f"Файл базы данных GeoIP не найден по пути: {db_path}") return "Файл базы данных GeoIP не найден" except Exception as e: - logger.error(f"Ошибка при определении местоположения: {e}") + # logger.error(f"Ошибка при определении местоположения: {e}") return "Местоположение недоступно" class ExternalDBSettings(models.Model): name = models.CharField(max_length=255, unique=True, help_text="Имя подключения для идентификации.") diff --git a/antifroud/tests.py b/antifroud/tests.py index 7ce503c2..e69de29b 100644 --- a/antifroud/tests.py +++ b/antifroud/tests.py @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/pms_integration/plugins/ecvi_pms.py b/pms_integration/plugins/ecvi_pms.py index bbc160c0..daf3a269 100644 --- a/pms_integration/plugins/ecvi_pms.py +++ b/pms_integration/plugins/ecvi_pms.py @@ -1,6 +1,156 @@ +# import logging +# import json +# import os +# from datetime import datetime, timedelta +# import requests +# from asgiref.sync import sync_to_async +# from hotels.models import Hotel, Reservation +# from .base_plugin import BasePMSPlugin + +# class EcviPMSPlugin(BasePMSPlugin): +# """ +# Плагин для интеграции с PMS Ecvi. +# """ + +# def __init__(self, hotel): +# super().__init__(hotel.pms) # Передаем PMS-конфигурацию в базовый класс +# self.hotel = hotel # Сохраняем объект отеля + +# # Проверка PMS-конфигурации +# if not self.hotel.pms: +# raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.") + +# # Инициализация параметров API +# self.api_url = self.hotel.pms.url +# self.token = self.hotel.pms.token +# self.username = self.hotel.pms.username +# self.password = self.hotel.pms.password + +# # Настройка логгера +# self.logger = logging.getLogger(self.__class__.__name__) +# handler_console = logging.StreamHandler() +# handler_file = logging.FileHandler('var/log/ecvi_pms_plugin.log') +# formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') +# handler_console.setFormatter(formatter) +# handler_file.setFormatter(formatter) +# self.logger.addHandler(handler_console) +# self.logger.addHandler(handler_file) +# self.logger.setLevel(logging.WARNING) + +# def get_default_parser_settings(self): +# """ +# Возвращает настройки парсера по умолчанию. +# """ +# return { +# "field_mapping": { +# "check_in": "checkin", +# "check_out": "checkout", +# "room_number": "room_name", +# "room_type_name": "room_type", +# "status": "occupancy", +# }, +# "date_format": "%Y-%m-%d %H:%M:%S" # Формат изменен на соответствующий данным +# } + +# async def _fetch_data(self): +# """ +# Получает данные из PMS API и сохраняет их в базу. +# """ +# headers = {"Content-Type": "application/json"} +# data = {"token": self.token} + +# try: +# # Запрос данных из PMS API +# response = await sync_to_async(requests.post)( +# self.api_url, headers=headers, json=data, auth=(self.username, self.password) +# ) +# response.raise_for_status() +# response_data = response.json() +# self.logger.debug(f"Полученные данные с API: {response_data}") +# return await self._process_data(response_data) +# except requests.exceptions.RequestException as e: +# self.logger.error(f"Ошибка API: {e}") +# return { +# "processed_intervals": 0, +# "processed_items": 0, +# "errors": [str(e)] +# } + + +# async def _process_data(self, data): +# """ +# Обрабатывает данные и сохраняет их в базу. +# """ +# processed_items = 0 +# errors = [] + +# date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"] # Поддержка нескольких форматов даты + +# for item in data: +# try: +# # Парсинг даты с поддержкой нескольких форматов +# checkin = self._parse_date(item['checkin'], date_formats) +# checkout = self._parse_date(item['checkout'], date_formats) + +# reservation, created = await sync_to_async(Reservation.objects.update_or_create)( +# reservation_id=item['task_id'], +# defaults={ +# 'room_number': item['room_name'], +# 'room_type': item['room_type'], +# 'check_in': checkin, +# 'check_out': checkout, +# 'status': item['occupancy'], +# 'hotel': self.hotel, +# } +# ) + +# if created: +# self.logger.debug(f"Создана новая резервация: {reservation.reservation_id}") +# else: +# self.logger.debug(f"Обновлена существующая резервация: {reservation.reservation_id}") + +# processed_items += 1 + +# except Exception as e: +# self.logger.error(f"Ошибка обработки записи: {e}") +# errors.append(str(e)) + +# return { +# "processed_intervals": 1, +# "processed_items": processed_items, +# "errors": errors +# } + +# @staticmethod +# def _parse_date(date_str, formats): +# """ +# Парсит дату, пытаясь использовать несколько форматов. +# """ +# for fmt in formats: +# try: +# return datetime.strptime(date_str, fmt) +# except ValueError: +# continue +# raise ValueError(f"Дата '{date_str}' не соответствует ожидаемым форматам: {formats}") + +# def validate_plugin(self): +# """ +# Проверка корректности реализации плагина. +# """ +# required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"] +# for method in required_methods: +# if not hasattr(self, method): +# raise ValueError(f"Плагин {type(self).__name__} не реализует метод {method}.") +# self.logger.debug(f"Плагин {self.__class__.__name__} прошел валидацию.") +# return True + + + import logging -from datetime import datetime, timedelta import requests +import json +import os +from datetime import datetime, timedelta from asgiref.sync import sync_to_async from hotels.models import Hotel, Reservation from .base_plugin import BasePMSPlugin @@ -11,20 +161,17 @@ class EcviPMSPlugin(BasePMSPlugin): """ def __init__(self, hotel): - super().__init__(hotel.pms) # Передаем PMS-конфигурацию в базовый класс - self.hotel = hotel # Сохраняем объект отеля + super().__init__(hotel.pms) + self.hotel = hotel - # Проверка PMS-конфигурации if not self.hotel.pms: raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.") - # Инициализация параметров API - self.api_url = self.hotel.pms.url + self.api_url = self.hotel.pms.url.rstrip("/") self.token = self.hotel.pms.token self.username = self.hotel.pms.username self.password = self.hotel.pms.password - # Настройка логгера self.logger = logging.getLogger(self.__class__.__name__) handler_console = logging.StreamHandler() handler_file = logging.FileHandler('var/log/ecvi_pms_plugin.log') @@ -36,9 +183,6 @@ class EcviPMSPlugin(BasePMSPlugin): self.logger.setLevel(logging.WARNING) def get_default_parser_settings(self): - """ - Возвращает настройки парсера по умолчанию. - """ return { "field_mapping": { "check_in": "checkin", @@ -47,24 +191,36 @@ class EcviPMSPlugin(BasePMSPlugin): "room_type_name": "room_type", "status": "occupancy", }, - "date_format": "%Y-%m-%d %H:%M:%S" # Формат изменен на соответствующий данным + "date_format": "%Y-%m-%d %H:%M:%S" } async def _fetch_data(self): - """ - Получает данные из PMS API и сохраняет их в базу. - """ headers = {"Content-Type": "application/json"} data = {"token": self.token} try: - # Запрос данных из PMS API response = await sync_to_async(requests.post)( self.api_url, headers=headers, json=data, auth=(self.username, self.password) ) response.raise_for_status() response_data = response.json() self.logger.debug(f"Полученные данные с API: {response_data}") + + # Группировка данных по номеру комнаты + structured_data = {} + for item in response_data: + room_number = item.get("room_name", "unknown") + if room_number not in structured_data: + structured_data[room_number] = [] + structured_data[room_number].append(item) + + # Сохранение данных во временный JSON-файл + temp_dir = os.path.join("temp", "ecvi") + os.makedirs(temp_dir, exist_ok=True) + temp_file = os.path.join(temp_dir, f"ecvi_data_{datetime.now().strftime('%Y%m%d%H%M%S')}.json") + with open(temp_file, 'w') as file: + json.dump(structured_data, file, indent=4, ensure_ascii=False) + return await self._process_data(response_data) except requests.exceptions.RequestException as e: self.logger.error(f"Ошибка API: {e}") @@ -75,19 +231,18 @@ class EcviPMSPlugin(BasePMSPlugin): } async def _process_data(self, data): - """ - Обрабатывает данные и сохраняет их в базу. - """ processed_items = 0 errors = [] - - date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"] # Поддержка нескольких форматов даты + date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"] for item in data: try: - # Парсинг даты с поддержкой нескольких форматов - checkin = self._parse_date(item['checkin'], date_formats) - checkout = self._parse_date(item['checkout'], date_formats) + checkin = item['checkin'] + checkout = item['checkout'] + if checkin in [None, "0000-00-00 00:00:00"] or checkout in [None, "0000-00-00 00:00:00"]: + continue + checkin = self._parse_date(checkin, date_formats) + checkout = self._parse_date(checkout, date_formats) reservation, created = await sync_to_async(Reservation.objects.update_or_create)( reservation_id=item['task_id'], @@ -100,14 +255,7 @@ class EcviPMSPlugin(BasePMSPlugin): 'hotel': self.hotel, } ) - - if created: - self.logger.debug(f"Создана новая резервация: {reservation.reservation_id}") - else: - self.logger.debug(f"Обновлена существующая резервация: {reservation.reservation_id}") - processed_items += 1 - except Exception as e: self.logger.error(f"Ошибка обработки записи: {e}") errors.append(str(e)) @@ -120,9 +268,6 @@ class EcviPMSPlugin(BasePMSPlugin): @staticmethod def _parse_date(date_str, formats): - """ - Парсит дату, пытаясь использовать несколько форматов. - """ for fmt in formats: try: return datetime.strptime(date_str, fmt) @@ -131,9 +276,6 @@ class EcviPMSPlugin(BasePMSPlugin): raise ValueError(f"Дата '{date_str}' не соответствует ожидаемым форматам: {formats}") def validate_plugin(self): - """ - Проверка корректности реализации плагина. - """ required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"] for method in required_methods: if not hasattr(self, method): diff --git a/staticfiles/admin/custom.css b/staticfiles/admin/custom.css new file mode 100644 index 00000000..a80e23a1 --- /dev/null +++ b/staticfiles/admin/custom.css @@ -0,0 +1,18 @@ +.ml-4 { + margin-left: 1rem !important; +} + +.ml-6 { + margin-left: 1.5rem !important; +} + +.nav-sidebar ul.nav-treeview .nav-link { + padding-left: 2rem; +} +.nav-sidebar .nav-link { + display: flex; +} +.nav-sidebar .nav-link .nav-icon { + margin-top: .2rem; + margin-right: .3rem; +}