From 24f1a4056177384ed789f49d8ceab2303e6b50bf Mon Sep 17 00:00:00 2001 From: trevor Date: Sun, 2 Feb 2025 09:26:44 +0900 Subject: [PATCH] RealtyCalendar plugin develop --- pms_integration/plugins/ecvi_pms.py | 149 +------- pms_integration/plugins/realtycalendar_pms.py | 348 ++++-------------- scheduler/test_module.py | 6 - scheduler/tests.py | 6 + 4 files changed, 80 insertions(+), 429 deletions(-) diff --git a/pms_integration/plugins/ecvi_pms.py b/pms_integration/plugins/ecvi_pms.py index daf3a269..bb1a32d9 100644 --- a/pms_integration/plugins/ecvi_pms.py +++ b/pms_integration/plugins/ecvi_pms.py @@ -1,151 +1,4 @@ -# 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 - - - +# ecvi_pms.py import logging import requests import json diff --git a/pms_integration/plugins/realtycalendar_pms.py b/pms_integration/plugins/realtycalendar_pms.py index 0b440aef..3a9b2f1b 100644 --- a/pms_integration/plugins/realtycalendar_pms.py +++ b/pms_integration/plugins/realtycalendar_pms.py @@ -1,313 +1,111 @@ +import logging import requests -import hashlib import json -from .base_plugin import BasePMSPlugin +import os from datetime import datetime, timedelta from asgiref.sync import sync_to_async -from touchh.utils.log import CustomLogger from hotels.models import Hotel, Reservation -from app_settings.models import GlobalHotelSettings -from django.utils import timezone +from .base_plugin import BasePMSPlugin -class RealtyCalendarPlugin(BasePMSPlugin): - def __init__(self, config): - super().__init__(config) - self.public_key = config.public_key - self.private_key = config.private_key - self.api_url = config.url.rstrip("/") - self.logger = CustomLogger(name="RealtyCalendarPlugin", log_level="DEBUG").get_logger() - if not self.public_key or not self.private_key: - raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar") +class RealtyCalendarPMSPlugin(BasePMSPlugin): + def __init__(self, hotel): + super().__init__(hotel.pms) + self.hotel = hotel + + if not self.hotel.pms: + raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.") + + self.api_url = self.hotel.pms.url.rstrip("/") + self.public_key = self.hotel.pms.public_key + self.private_key = self.hotel.pms.private_key + + self.logger = logging.getLogger(self.__class__.__name__) + handler_console = logging.StreamHandler() + handler_file = logging.FileHandler('var/log/realtycalendar_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 { - "date_format": "%Y-%m-%dT%H:%M:%S", - "timezone": "UTC" + "field_mapping": { + "check_in": "begin_date", + "check_out": "end_date", + "room_number": "apartment_id", + "room_type_name": "notes", + "status": "status", + }, + "date_format": "%Y-%m-%d %H:%M:%S" } - def _get_sorted_keys(self, obj): - sorted_keys = sorted(obj.keys()) - self.logger.debug(f"Отсортированные ключи: {sorted_keys}") - return sorted_keys - - def _generate_data_string(self, obj): - sorted_keys = self._get_sorted_keys(obj) - string = "".join(f"{key}={obj[key]}" for key in sorted_keys) - self.logger.debug(f"Сформированная строка данных: {string}") - return string + self.private_key - - def _generate_md5(self, string): - md5_hash = hashlib.md5(string.encode("utf-8")).hexdigest() - self.logger.debug(f"Сформированный MD5-хеш: {md5_hash}") - return md5_hash - - def _generate_sign(self, data): - data_string = self._generate_data_string(data) - self.logger.debug(f"Строка для подписи: {data_string}") - sign = self._generate_md5(data_string) - self.logger.debug(f"Подпись: {sign}") - return sign - - # async def _fetch_data(self): - # self.logger.debug("Начало выполнения функции _fetch_data") - # base_url = f"{self.api_url}/api/v1/bookings/{self.public_key}/" - # headers = { - # "Accept": "application/json", - # "Content-Type": "application/json", - # } - - # now = datetime.now() - # data = { - # "begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"), - # "end_date": now.strftime("%Y-%m-%d"), - # } - # data["sign"] = self._generate_sign(data) - - # try: - # response = requests.post(url=base_url, headers=headers, json=data) - # self.logger.debug(f"Статус ответа: {response.status_code}") - - # if response.status_code != 200: - # self.logger.error(f"Ошибка API: {response.status_code}, {response.text}") - # raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}") - - # try: - # response_data = response.json() - # bookings = response_data.get("bookings", []) - # # self.logger.debug(f"Полученные данные: {bookings}") - - # if not isinstance(bookings, list): - # self.logger.error(f"Ожидался список, но получен: {type(bookings)}") - # raise ValueError("Некорректный формат данных для bookings") - # except json.JSONDecodeError as e: - # self.logger.error(f"Ошибка декодирования JSON: {e}") - # raise ValueError("Ответ не является корректным JSON.") - # except Exception as e: - # self.logger.error(f"Ошибка обработки ответа API: {e}") - # raise - - # except Exception as e: - # self.logger.error(f"Ошибка запроса к API RealtyCalendar: {e}") - # raise - - # try: - # hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config) - # hotel_tz = hotel.timezone - # self.logger.debug(f"Настройки отеля: {hotel.name}, Timezone: {hotel_tz}") - - # hotel_settings = await sync_to_async(GlobalHotelSettings.objects.first)() - # check_in_time = hotel_settings.check_in_time.strftime("%H:%M:%S") if hotel_settings else "14:00:00" - # check_out_time = hotel_settings.check_out_time.strftime("%H:%M:%S") if hotel_settings else "12:00:00" - # except Exception as e: - # self.logger.error(f"Ошибка получения настроек отеля: {e}") - # check_in_time, check_out_time = "14:00:00", "12:00:00" - - # filtered_data = [] - # for item in bookings: - # try: - # if not isinstance(item, dict): - # self.logger.error(f"Некорректный формат элемента: {item}") - # continue - - # reservation_id = item.get('id') - # if not reservation_id: - # self.logger.error(f"ID резервации отсутствует: {item}") - # continue - - # begin_date = item.get('begin_date') - # end_date = item.get('end_date') - # if not begin_date or not end_date: - # self.logger.error(f"Отсутствуют даты в записи: {item}") - # continue - - # checkin = timezone.make_aware( - # datetime.strptime(f"{begin_date} {check_in_time}", "%Y-%m-%d %H:%M:%S") - # ) - # checkout = timezone.make_aware( - # datetime.strptime(f"{end_date} {check_out_time}", "%Y-%m-%d %H:%M:%S") - # ) - - # filtered_data.append({ - # 'reservation_id': reservation_id, - # 'checkin': checkin, - # 'checkout': checkout, - # 'room_number': item.get('apartment_id'), - # 'room_type': item.get('notes', 'Описание отсутствует'), - # 'status': item.get('status') - # }) - # except Exception as e: - # self.logger.error(f"Ошибка обработки элемента: {e}") - - # # self.logger.debug(f"Отфильтрованные данные: {filtered_data}") - # await self._save_to_db(filtered_data) async def _fetch_data(self): - self.logger.debug("Начало выполнения функции _fetch_data") - base_url = f"{self.api_url}/api/v1/bookings/{self.public_key}/" - headers = { - "Accept": "application/json", - "Content-Type": "application/json", - } - + headers = {"Accept": "application/json", "Content-Type": "application/json"} now = datetime.now() data = { "begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"), "end_date": now.strftime("%Y-%m-%d"), + "sign": self._generate_sign() } - data["sign"] = self._generate_sign(data) try: - response = requests.post(url=base_url, headers=headers, json=data) - self.logger.debug(f"Статус ответа: {response.status_code}") - - if response.status_code != 200: - self.logger.error(f"Ошибка API: {response.status_code}, {response.text}") - return { - "processed_intervals": 0, - "processed_items": 0, - "errors": [f"Ошибка API RealtyCalendar: {response.status_code}"] - } - + response = await sync_to_async(requests.post)( + f"{self.api_url}/api/v1/bookings/{self.public_key}/", + headers=headers, json=data + ) + response.raise_for_status() response_data = response.json() - bookings = response_data.get("bookings", []) + 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)]} - if not isinstance(bookings, list): - self.logger.error(f"Ожидался список, но получен: {type(bookings)}") - return { - "processed_intervals": 0, - "processed_items": 0, - "errors": ["Некорректный формат данных для bookings"] - } - - except json.JSONDecodeError as e: - self.logger.error(f"Ошибка декодирования JSON: {e}") - return { - "processed_intervals": 0, - "processed_items": 0, - "errors": ["Ответ не является корректным JSON."] - } - except Exception as e: - self.logger.error(f"Ошибка запроса к API RealtyCalendar: {e}") - return { - "processed_intervals": 0, - "processed_items": 0, - "errors": [str(e)] - } - - # Получение настроек отеля - try: - hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config) - hotel_tz = hotel.timezone - self.logger.debug(f"Настройки отеля: {hotel.name}, Timezone: {hotel_tz}") - - hotel_settings = await sync_to_async(GlobalHotelSettings.objects.first)() - check_in_time = hotel_settings.check_in_time.strftime("%H:%M:%S") if hotel_settings else "14:00:00" - check_out_time = hotel_settings.check_out_time.strftime("%H:%M:%S") if hotel_settings else "12:00:00" - except Exception as e: - self.logger.error(f"Ошибка получения настроек отеля: {e}") - check_in_time, check_out_time = "14:00:00", "12:00:00" - - # Обработка записей + async def _process_data(self, data): processed_items = 0 errors = [] - filtered_data = [] + date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"] - for item in bookings: + for item in data.get("bookings", []): try: - if not isinstance(item, dict): - raise ValueError(f"Некорректный формат элемента: {item}") + checkin = self._parse_date(item['begin_date'], date_formats) + checkout = self._parse_date(item['end_date'], date_formats) - reservation_id = item.get('id') - if not reservation_id: - raise ValueError(f"ID резервации отсутствует: {item}") - - begin_date = item.get('begin_date') - end_date = item.get('end_date') - if not begin_date or not end_date: - raise ValueError(f"Отсутствуют даты в записи: {item}") - - checkin = timezone.make_aware( - datetime.strptime(f"{begin_date} {check_in_time}", "%Y-%m-%d %H:%M:%S") + reservation, created = await sync_to_async(Reservation.objects.update_or_create)( + reservation_id=item['id'], + defaults={ + 'room_number': item['apartment_id'], + 'room_type': item.get('notes', 'Описание отсутствует'), + 'check_in': checkin, + 'check_out': checkout, + 'status': item['status'], + 'hotel': self.hotel, + } ) - checkout = timezone.make_aware( - datetime.strptime(f"{end_date} {check_out_time}", "%Y-%m-%d %H:%M:%S") - ) - - filtered_data.append({ - 'reservation_id': reservation_id, - 'checkin': checkin, - 'checkout': checkout, - 'room_number': item.get('apartment_id'), - 'room_type': item.get('notes', 'Описание отсутствует'), - 'status': item.get('status') - }) processed_items += 1 except Exception as e: - self.logger.error(f"Ошибка обработки элемента: {e}") + self.logger.error(f"Ошибка обработки записи: {e}") errors.append(str(e)) - # Сохранение в БД - try: - await self._save_to_db(filtered_data) - except Exception as e: - self.logger.error(f"Ошибка сохранения данных в БД: {e}") - errors.append(f"Ошибка сохранения данных в БД: {str(e)}") + return {"processed_intervals": 1, "processed_items": processed_items, "errors": errors} - # Формирование отчета - report = { - "processed_intervals": 1, # Пример значения - "processed_items": processed_items, - "errors": errors - } - self.logger.debug(f"Сформированный отчет: {report}") - return report + def _generate_sign(self): + return hashlib.md5((self.public_key + self.private_key).encode("utf-8")).hexdigest() - - async def _save_to_db(self, data): - if not isinstance(data, list): - self.logger.error(f"Ожидался список записей, но получен {type(data).__name__}") - return - - for index, item in enumerate(data, start=1): + @staticmethod + def _parse_date(date_str, formats): + for fmt in formats: try: - hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config) - reservation_id = item.get('reservation_id') - if not reservation_id: - self.logger.error(f"Пропущена запись {index}: отсутствует 'id'") - continue - - existing_reservation = await sync_to_async(Reservation.objects.filter)(reservation_id=reservation_id) - existing_reservation = await sync_to_async(existing_reservation.first)() - - defaults = { - 'room_number': item['room_number'], - 'room_type': item['room_type'], - 'check_in': item['checkin'], - 'check_out': item['checkout'], - 'status': item['status'], - 'hotel': hotel - } - - if existing_reservation: - await sync_to_async(Reservation.objects.update_or_create)( - reservation_id=reservation_id, defaults=defaults - ) - self.logger.debug(f"Резервация {reservation_id} обновлена. ") - else: - await sync_to_async(Reservation.objects.create)( - reservation_id=reservation_id, **defaults - ) - self.logger.debug(f"Создана новая резервация {reservation_id}") - - except Exception as e: - self.logger.error(f"Ошибка при обработке записи {index}: {e}") + 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 m in required_methods: - if not hasattr(self, m): - raise ValueError(f"Плагин {type(self).__name__} не реализует метод {m}.") - self.logger.debug(f"Плагин {self.__class__.__name__} прошел валидацию.") + 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 diff --git a/scheduler/test_module.py b/scheduler/test_module.py index 09ac24cc..e69de29b 100644 --- a/scheduler/test_module.py +++ b/scheduler/test_module.py @@ -1,6 +0,0 @@ -def test_function(): - """тестовая функция для проверки планировщика - - """ - print("Hello, World!") - return "Hello, World!" diff --git a/scheduler/tests.py b/scheduler/tests.py index 7ce503c2..fefa7a59 100644 --- a/scheduler/tests.py +++ b/scheduler/tests.py @@ -1,3 +1,9 @@ from django.test import TestCase # Create your tests here. +def test_function(): + """тестовая функция для проверки планировщика + + """ + print("Hello, World!") + return "Hello, World!"