diff --git a/pms_integration/manager.py b/pms_integration/manager.py index 8e4065a5..e6f6d0e7 100644 --- a/pms_integration/manager.py +++ b/pms_integration/manager.py @@ -56,6 +56,9 @@ class PMSIntegrationManager: if pms_name == "ecvi_intermark" or pms_name == "ecvi": from pms_integration.plugins.ecvi_pms import EcviPMSPlugin self.plugin = EcviPMSPlugin(self.hotel) + elif pms_name == "shelter" or pms_name == "shelter": + from pms_integration.plugins.shelter_pms import ShelterPMSPlugin + self.plugin = ShelterPMSPlugin(self.hotel) else: raise ValueError(f"Неизвестный PMS: {pms_name}") def fetch_data(self): diff --git a/pms_integration/plugins/ecvi_pms.py b/pms_integration/plugins/ecvi_pms.py index 777c37b5..63ef2121 100644 --- a/pms_integration/plugins/ecvi_pms.py +++ b/pms_integration/plugins/ecvi_pms.py @@ -33,7 +33,7 @@ class EcviPMSPlugin(BasePMSPlugin): handler_file.setFormatter(formatter) self.logger.addHandler(handler_console) self.logger.addHandler(handler_file) - self.logger.setLevel(logging.DEBUG) + self.logger.setLevel(logging.WARNING) def get_default_parser_settings(self): """ diff --git a/pms_integration/plugins/shelter_pms.py b/pms_integration/plugins/shelter_pms.py index 8b81fbb3..b69dafb7 100644 --- a/pms_integration/plugins/shelter_pms.py +++ b/pms_integration/plugins/shelter_pms.py @@ -1,34 +1,39 @@ import logging import requests +import json +import os from datetime import datetime, timedelta from asgiref.sync import sync_to_async -from pms_integration.models import PMSConfiguration from hotels.models import Hotel, Reservation from .base_plugin import BasePMSPlugin - -class EcviPMSPlugin(BasePMSPlugin): +class ShelterPMSPlugin(BasePMSPlugin): """ - Плагин для интеграции с PMS Ecvi (интерфейс для получения данных об отеле). + Плагин для интеграции с PMS Shelter. """ - def __init__(self, pms_config): - super().__init__(pms_config) - - # Инициализация логгера - self.logger = logging.getLogger(self.__class__.__name__) # Логгер с именем класса - handler = logging.StreamHandler() # Потоковый обработчик для вывода в консоль - formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') - handler.setFormatter(formatter) - self.logger.addHandler(handler) - self.logger.setLevel(logging.DEBUG) # Уровень логирования + 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 = pms_config.url - self.token = pms_config.token - self.username = pms_config.username - self.password = pms_config.password - self.pagination_count = 50 # Максимальное количество записей на страницу (если используется пагинация) + self.api_url = self.hotel.pms.url + self.token = self.hotel.pms.token + + # Настройка логгера + self.logger = logging.getLogger(self.__class__.__name__) + handler_console = logging.StreamHandler() + handler_file = logging.FileHandler('shelter_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.DEBUG) def get_default_parser_settings(self): """ @@ -36,62 +41,145 @@ class EcviPMSPlugin(BasePMSPlugin): """ return { "field_mapping": { - "check_in": "checkin", - "check_out": "checkout", - "room_number": "room_name", - "room_type_name": "room_type", - "status": "occupancy", + "check_in": "from", + "check_out": "until", + "room_number": "roomNumber", + "room_type_name": "roomTypeName", + "status": "checkInStatus", }, "date_format": "%Y-%m-%dT%H:%M:%S" } async def _fetch_data(self): """ - Получает данные из PMS API, фильтрует и сохраняет в базу данных. + Получает данные из PMS API и сохраняет их в базу. """ now = datetime.now() - current_date = now.strftime('%Y-%m-%d') - yesterday_date = (now - timedelta(days=1)).strftime('%Y-%m-%d') + start_date = (now - timedelta(days=60)).strftime('%Y-%m-%dT%H:%M:%SZ') + end_date = (now + timedelta(days=60)).strftime('%Y-%m-%dT%H:%M:%SZ') headers = { "Content-Type": "application/json", + "Accept": "text/plain", + "Authorization": f"Bearer {self.token}" } - data = { - "token": self.token, - } + + from_index = 0 + count_per_request = 50 + all_items = [] 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() # Если ошибка, выбросит исключение - data = response.json() # Преобразуем ответ в JSON - self.logger.debug(f"Получены данные с API: {data}") - except requests.exceptions.RequestException as e: - self.logger.error(f"Ошибка запроса: {e}") - return [] - - # Фильтрация данных - filtered_data = [] - for item in data: - if item.get('occupancy') in ['проживание', 'под выезд', 'под заезд']: - filtered_item = { - 'checkin': datetime.strptime(item.get('checkin'), '%Y-%m-%d %H:%M:%S'), - 'checkout': datetime.strptime(item.get('checkout'), '%Y-%m-%d %H:%M:%S'), - 'room_number': item.get('room_name'), - 'room_type': item.get('room_type'), - 'status': item.get('occupancy') + while True: + data = { + "from": start_date, + "until": end_date, + "pagination": { + "from": from_index, + "count": count_per_request + } } - filtered_data.append(filtered_item) - # Логируем результат фильтрации - self.logger.debug(f"Отфильтрованные данные: {filtered_data}") + response = await sync_to_async(requests.post)(self.api_url, headers=headers, data=json.dumps(data)) + response.raise_for_status() + response_data = response.json() - # Сохранение данных в базу данных - for item in filtered_data: - await self._save_to_db(item) + items = response_data.get("items", []) + all_items.extend(items) - self.logger.debug(f"Данные успешно сохранены.") - return filtered_data + total_count = response_data.get("count", 0) + from_index += len(items) + + if from_index >= total_count: + break + + self.logger.info(f"Получено записей: {len(all_items)}") + + # Сохранение данных во временный файл + temp_dir = os.path.join("temp", "shelter") + os.makedirs(temp_dir, exist_ok=True) + temp_file = os.path.join(temp_dir, f"shelter_data_{datetime.now().strftime('%Y%m%d%H%M%S')}.json") + + with open(temp_file, 'w') as file: + json.dump(all_items, file) + + self.logger.info(f"Данные сохранены во временный файл: {temp_file}") + + return await self._process_data(all_items) + + 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['from'], date_formats) + checkout = self._parse_date(item['until'], date_formats) + + reservation, created = await sync_to_async(Reservation.objects.update_or_create)( + reservation_id=item['id'], + defaults={ + 'room_number': item.get('roomNumber'), + 'room_type': item.get('roomTypeName'), + 'check_in': checkin, + 'check_out': checkout, + 'status': item.get('checkInStatus'), + '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 async def _save_to_db(self, item): """