# import requests # import json # from datetime import datetime, timedelta # from asgiref.sync import sync_to_async # from .base_plugin import BasePMSPlugin # from pms_integration.models import PMSConfiguration # from hotels.models import Reservation, Hotel # from touchh.utils.log import CustomLogger # import logging # import logging # # Настройка логирования # logging.basicConfig( # level=logging.WARNING, # Установите уровень логов для всех обработчиков # format='%(asctime)s [%(levelname)s] %(message)s', # handlers=[ # logging.FileHandler("bnovo_plugin.log"), # Логи пишутся в файл # logging.StreamHandler() # Логи выводятся в консоль # ] # ) # # Создаем отдельный логгер для консоли с уровнем INFO # console_handler = logging.StreamHandler() # console_handler.setLevel(logging.INFO) # Меняем уровень логов для консоли # console_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) # # Основной логгер # logger = logging.getLogger("BnovoPMS Plugin") # logger.addHandler(console_handler) # logger.setLevel(logging.WARNING) # Основной уровень логов # class BnovoPMSPlugin(BasePMSPlugin): # """Плагин для работы с PMS Bnovo.""" # def __init__(self, hotel): # super().__init__(hotel) # if not isinstance(hotel, Hotel): # logger.error("Ожидался объект Hotel, но получен другой тип.") # raise ValueError("Для инициализации плагина требуется объект Hotel.") # self.hotel = hotel # self.pms_config = hotel.pms # Связь отеля с PMSConfiguration # if not self.pms_config: # logger.error(f"Отель {hotel.id} не связан с конфигурацией PMS.") # raise ValueError(f"Отель {hotel.id} не связан с конфигурацией PMS.") # self.api_url = self.pms_config.url.rstrip("/") # self.username = self.pms_config.username # self.password = self.pms_config.password # self.token = None # if not self.api_url: # logger.error("Не указан URL для работы плагина.") # raise ValueError("Не указан URL для работы плагина.") # if not self.username or not self.password: # logger.error("Не указаны логин или пароль для авторизации.") # raise ValueError("Не указаны логин или пароль для авторизации.") # def get_default_parser_settings(self): # """Возвращает настройки по умолчанию для обработки данных.""" # logger.debug("Получение настроек парсера по умолчанию.") # return { # "date_format": "%Y-%m-%dT%H:%M:%S", # "timezone": "UTC" # } # async def _get_stored_token(self): # """Получение токена из конфигурации PMS отеля.""" # try: # logger.debug(f"Попытка получения токена для отеля {self.hotel.id}.") # token = self.pms_config.token # logger.debug(f"Токен из базы данных: {token}") # return token # except Exception as e: # logger.warning(f"Ошибка при получении токена для отеля {self.hotel.id}: {e}") # return None # async def _save_token_to_db(self, sid): # """Сохраняет токен (SID) в конфигурации PMS отеля.""" # try: # logger.debug(f"Сохранение токена для отеля {self.hotel.id}: {sid}") # self.pms_config.token = sid # await sync_to_async(self.pms_config.save)() # logger.debug("Токен успешно сохранен.") # except Exception as e: # logger.error(f"Ошибка сохранения токена для отеля {self.hotel.id}: {e}") # async def _ensure_token(self): # """ # Убеждается, что токен (SID) доступен. Если его нет, выполняет авторизацию. # """ # logger.debug(f"Проверка токена для отеля {self.hotel.id}.") # if not self.token: # self.token = await self._get_stored_token() # if not self.token: # logger.info("Токен отсутствует, выполняем авторизацию.") # await self._fetch_session() # else: # logger.debug(f"Используется сохраненный токен: {self.token}") # def _get_auth_headers(self): # """Создает заголовки авторизации.""" # logger.debug("Создание заголовков авторизации.") # headers = { # "Content-Type": "application/json", # "Accept": "application/json", # } # if self.token: # headers["Cookie"] = f"SID={self.token}" # logger.debug(f"Добавлен токен в заголовки: {self.token}") # return headers # async def _fetch_session(self): # """Получение нового токена (SID) через запрос.""" # url = f"{self.api_url}/" # payload = {"username": self.username, "password": self.password} # headers = self._get_auth_headers() # logger.debug(f"Авторизация по адресу: {url} с данными: {json.dumps(payload)}") # response = requests.post(url, json=payload, headers=headers, allow_redirects=False) # logger.debug(f"Ответ авторизации: статус {response.status_code}, заголовки {response.headers}") # if response.status_code == 302: # cookies = response.cookies.get_dict() # sid = cookies.get("SID") # if sid: # self.token = sid # logger.debug(f"Получен новый SID: {sid}") # await self._save_token_to_db(sid) # else: # logger.error("Не удалось извлечь SID из ответа.") # raise ValueError("Не удалось извлечь SID из ответа.") # else: # logger.error(f"Ошибка авторизации: {response.status_code}, {response.text}") # raise ValueError(f"Ошибка авторизации: {response.status_code}, {response.text}") # async def _fetch_account_data(self): # """Получение данных аккаунта через эндпоинт /account/current.""" # logger.info(f"Начало получения данных аккаунта для отеля {self.hotel.id}.") # self.token = await self._get_stored_token() # if not self.token: # logger.info("Токен отсутствует, выполняем авторизацию.") # await self._fetch_session() # url = f"{self.api_url}/account/current" # headers = self._get_auth_headers() # logger.debug(f"Выполнение запроса к {url}") # response = requests.get(url, headers=headers) # if response.status_code != 200: # logger.error(f"Ошибка при запросе данных аккаунта: {response.status_code}, {response.text}") # raise ValueError("Ошибка запроса к /account/current") # try: # account_data = response.json() # logger.debug(f"Полученные данные аккаунта:") # except json.JSONDecodeError as e: # logger.error(f"Ошибка декодирования JSON: {e}") # raise ValueError(f"Ошибка декодирования JSON: {e}") # return account_data # async def _fetch_and_log_account_data(self): # """Вызов метода _fetch_account_data и вывод результата в лог.""" # logger.info(f"Запуск получения и логирования данных аккаунта для отеля {self.hotel.id}.") # try: # account_data = await self._fetch_account_data() # logger.info(f"Успешно полученные данные аккаунта:") # return account_data # except Exception as e: # logger.error(f"Ошибка при получении данных аккаунта: {e}") # raise # async def _fetch_data_with_account_info(self): # """Получение данных аккаунта и бронирований.""" # logger.info(f"Запуск процесса получения данных аккаунта и бронирований для отеля {self.hotel.id}.") # try: # account_data = await self.fetch_and_log_account_data() # logger.info("Данные аккаунта успешно получены, продолжение с бронированиями.") # await self.__fetch_data() # except Exception as e: # logger.error(f"Ошибка при выполнении полной операции: {e}") # async def _fetch_paginated_data(self): # """ # Получает все данные с API, обрабатывая страницы с пагинацией. # """ # logger.info("Начало получения данных с пагинацией.") # await self._ensure_token() # url = f"{self.api_url}/dashboard" # headers = self._get_auth_headers() # now = datetime.now() # create_from = (now - timedelta(days=1)).strftime("%d.%m.%Y") # create_to = now.strftime("%d.%m.%Y") # params = { # "create_from": create_from, # "create_to": create_to, # "advanced_search": 2, # "c": 100, # "page": 1, # "order_by": "create_date.asc", # } # all_bookings = [] # try: # while True: # logger.debug(f"Запрос к {url} с параметрами: {json.dumps(params, indent=2)}") # response = requests.get(url, headers=headers, params=params, allow_redirects=False) # if response.status_code == 302: # logger.warning("Получен код 302. Перенаправление.") # await self._fetch_session() # headers = self._get_auth_headers() # response = requests.get(url, headers=headers, params=params) # if response.status_code != 200: # logger.error(f"Ошибка при запросе: {response.status_code}, {response.text}") # raise ValueError("Ошибка при получении данных.") # data = response.json() # bookings = data.get("bookings", []) # all_bookings.extend(bookings) # # Проверка окончания пагинации # pages_info = data.get("pages", {}) # current_page = pages_info.get("current_page", 1) # total_pages = pages_info.get("total_pages", 1) # logger.debug(f"Информация о страницах: текущая {current_page}, всего {total_pages}") # if current_page >= total_pages: # break # params["page"] += 1 # except Exception as e: # logger.error(f"Ошибка при обработке данных: {e}") # raise # logger.info(f"Всего бронирований: {len(all_bookings)}") # return all_bookings # async def _save_hotel_data(self, hotel_data): # """ # Сохраняет данные об отеле в базу. # """ # try: # hotel_id = hotel_data.get("id") # if not hotel_id: # logger.warning("Данные об отеле не содержат идентификатор.") # return # await sync_to_async(Hotel.objects.update_or_create)( # external_id=hotel_id, # defaults={ # "name": hotel_data.get("name"), # "address": hotel_data.get("address"), # "city": hotel_data.get("city"), # "timezone": hotel_data.get("timezone"), # "rating": hotel_data.get("rating"), # "phone": hotel_data.get("phone"), # "email": hotel_data.get("email"), # "country": hotel_data.get("country"), # "booking_url": hotel_data.get("booking_url"), # "tripadvisor_url": hotel_data.get("tripadvisor_url"), # }, # ) # logger.info(f"Информация об отеле {hotel_id} успешно обновлена.") # except Exception as e: # logger.error(f"Ошибка при сохранении данных об отеле: {e}") # async def _fetch_data(self): # """ # Получает данные о бронированиях с API и возвращает их. # """ # logger.info("Начало процесса получения данных о бронированиях.") # try: # await self._ensure_token() # Проверка токена # url = f"{self.api_url}/dashboard" # headers = self._get_auth_headers() # now = datetime.now() # create_from = (now - timedelta(days=1)).strftime("%d.%m.%Y") # create_to = now.strftime("%d.%m.%Y") # params = { # "create_from": create_from, # "create_to": create_to, # "advanced_search": 2, # "c": 100, # "page": 1, # "order_by": "create_date.asc", # } # all_data = [] # while True: # logger.debug(f"Запрос к {url} с параметрами: {json.dumps(params, indent=2)}") # response = requests.get(url, headers=headers, params=params, allow_redirects=False) # if response.status_code == 302: # logger.warning("Получен код 302. Перенаправление.") # await self._fetch_session() # Обновляем токен # headers = self._get_auth_headers() # continue # if response.status_code != 200: # logger.error(f"Ошибка при запросе: {response.status_code}, {response.text}") # raise ValueError(f"Ошибка при получении данных: {response.text}") # try: # data = response.json() # except json.JSONDecodeError as e: # logger.error(f"Ошибка декодирования JSON: {e}. Ответ: {response.text}") # raise ValueError(f"Ошибка декодирования JSON: {e}") # bookings = data.get("bookings", []) # logger.debug(f"Получено бронирований: {len(bookings)}") # all_data.extend(bookings) # # Проверка окончания пагинации # pages_info = data.get("pages", {}) # current_page = pages_info.get("current_page", 1) # total_pages = pages_info.get("total_pages", 1) # logger.debug(f"Текущая страница: {current_page}, всего страниц: {total_pages}") # if current_page >= total_pages: # break # params["page"] += 1 # if not all_data: # logger.error("Полученные данные пусты или отсутствуют бронирования.") # raise ValueError("API вернуло пустые данные.") # logger.info(f"Всего бронирований: {len(all_data)}") # return all_data # except Exception as e: # logger.error(f"Ошибка при получении данных: {e}") # raise # async def _process_and_save_data(self, data): # """ # Обрабатывает и сохраняет данные о бронированиях в базу. # """ # logger.info("Начало обработки данных о бронированиях для сохранения в базу.") # processed_items = 0 # errors = [] # for record in data: # try: # booking_id = record.get("id") # room_number = record.get("current_room") # arrival = record.get("arrival") # departure = record.get("departure") # status = record.get("status_name") # # Проверка обязательных данных # if not (booking_id and room_number and arrival and departure and status): # logger.warning(f"Пропуск записи из-за отсутствия обязательных данных: {record}") # continue # # Сохраняем или обновляем запись в базе данных # reservation, created = await sync_to_async(Reservation.objects.update_or_create)( # external_id=booking_id, # defaults={ # "hotel": self.hotel, # "status": status, # "room_number": room_number, # "check_in": arrival, # "check_out": departure, # }, # ) # if created: # logger.info(f"Создана новая запись бронирования: {reservation}") # else: # logger.info(f"Обновлено существующее бронирование: {reservation}") # processed_items += 1 # except Exception as e: # logger.error(f"Ошибка обработки бронирования {record.get('id')}: {e}") # errors.append(str(e)) # logger.info(f"Обработано бронирований: {processed_items}, ошибок: {len(errors)}") # return {"processed_items": processed_items, "errors": errors} # async def fetch_and_process_data(self): # """ # Загружает данные с API и сохраняет их в базу. # """ # logger.info("Начало процесса загрузки и обработки данных.") # try: # data = await self._fetch_paginated_data() # report = await self._process_and_save_data(data) # logger.info(f"Загрузка и обработка завершены. Отчет: {report}") # return report # except Exception as e: # logger.error(f"Ошибка в процессе загрузки и обработки данных: {e}") # raise import requests import json from datetime import datetime, timedelta from asgiref.sync import sync_to_async from .base_plugin import BasePMSPlugin from pms_integration.models import PMSConfiguration from hotels.models import Reservation, Hotel import logging # Настройка логирования logging.basicConfig( level=logging.WARNING, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[ logging.FileHandler("var/log/bnovo_plugin.log"), # Логи пишутся в файл logging.StreamHandler() # Логи выводятся в консоль ] ) logger = logging.getLogger("BnovoPMS Plugin") logger.setLevel(logging.INFO) class BnovoPMSPlugin(BasePMSPlugin): """Плагин для работы с PMS Bnovo.""" def __init__(self, hotel): super().__init__(hotel) self.hotel = hotel self.pms_config = hotel.pms self.api_url = self.pms_config.url.rstrip("/") self.username = self.pms_config.username self.password = self.pms_config.password self.token = None def get_default_parser_settings(self): """ Возвращает настройки по умолчанию для обработки данных. """ logger.debug("Получение настроек парсера по умолчанию.") return { "date_format": "%Y-%m-%dT%H:%M:%S", "timezone": "UTC" } async def _get_stored_token(self): """ Получает токен из конфигурации PMS отеля. """ try: logger.debug(f"Попытка получения токена для отеля {self.hotel.id}.") token = self.pms_config.token if not token: logger.info("Токен отсутствует в конфигурации.") else: logger.debug(f"Токен найден: {token}") return token except Exception as e: logger.error(f"Ошибка при получении токена для отеля {self.hotel.id}: {e}") return None def _get_auth_headers(self): """ Создает заголовки авторизации для запросов к API. """ logger.debug("Создание заголовков авторизации.") headers = { "Content-Type": "application/json", "Accept": "application/json", } if self.token: headers["Cookie"] = f"SID={self.token}" logger.debug(f"Добавлен токен в заголовки: {self.token}") else: logger.warning("Токен отсутствует, запрос может быть неавторизованным.") return headers async def _ensure_token(self): """ Убеждается, что токен (SID) доступен. Если его нет, выполняет авторизацию. """ logger.debug(f"Проверка токена для отеля {self.hotel.id}.") if not self.token: self.token = await self._get_stored_token() if not self.token: logger.info("Токен отсутствует, выполняем авторизацию.") await self._fetch_session() else: logger.debug(f"Используется сохраненный токен: {self.token}") async def _save_token_to_db(self, sid): """ Сохраняет токен (SID) в конфигурации PMS отеля. """ try: logger.debug(f"Сохранение токена для отеля {self.hotel.id}: {sid}") self.pms_config.token = sid await sync_to_async(self.pms_config.save)() logger.info(f"Токен {sid} успешно сохранен в базу данных.") except Exception as e: logger.error(f"Ошибка сохранения токена для отеля {self.hotel.id}: {e}") raise async def _fetch_session(self): """Получение токена (SID) через авторизацию.""" url = f"{self.api_url}/" payload = {"username": self.username, "password": self.password} headers = {"Content-Type": "application/json"} await self._save_token_to_db(self.token) response = requests.post(url, json=payload, headers=headers, allow_redirects=False) if response.status_code == 302: self.token = response.cookies.get("SID") await self._save_token_to_db(self.token) else: logger.error(f"Ошибка авторизации: {response.status_code}, {response.text}") raise ValueError("Ошибка авторизации") async def _fetch_paginated_data(self): """ Получает все данные с API, обрабатывая страницы с пагинацией. """ logger.info("Начало получения данных с пагинацией.") await self._ensure_token() url = f"{self.api_url}/dashboard" headers = self._get_auth_headers() now = datetime.now() create_from = (now - timedelta(days=7)).strftime("%d.%m.%Y") # Получаем данные за последнюю неделю create_to = now.strftime("%d.%m.%Y") params = { "create_from": create_from, "create_to": create_to, "advanced_search": 2, "c": 100, "page": 1, "order_by": "create_date.asc", } all_bookings = [] try: while True: logger.debug(f"Запрос к {url} с параметрами: {json.dumps(params, indent=2)}") response = requests.get(url, headers=headers, params=params, allow_redirects=False) if response.status_code == 302: logger.warning("Получен код 302. Перенаправление.") await self._fetch_session() headers = self._get_auth_headers() continue if response.status_code != 200: logger.error(f"Ошибка при запросе: {response.status_code}, {response.text}") raise ValueError(f"Ошибка при получении данных: {response.text}") data = response.json() logger.debug(f"Полученный ответ API: {json.dumps(data, indent=2, ensure_ascii=False)}") bookings = data.get("bookings", []) rooms = data.get("rooms", []) logger.debug(f'bookings: {bookings}\n rooms: {rooms}') all_bookings.extend(bookings) pages_info = data.get("pages", {}) current_page = pages_info.get("current_page", 1) total_pages = pages_info.get("total_pages", 1) if current_page >= total_pages: break params["page"] += 1 except Exception as e: logger.error(f"Ошибка при обработке данных: {e}") raise async def _process_and_save_bookings(self, bookings): """ Обрабатывает и сохраняет бронирования в базу. """ logger.info("Начало обработки данных о бронированиях для сохранения в базу.") processed_items = 0 errors = [] for record in bookings: try: booking_id = record.get("id") room_number = record.get("current_room") arrival = record.get("arrival") departure = record.get("departure") status = record.get("status_name") # Проверка обязательных данных if not (booking_id and room_number and arrival and departure and status): logger.warning(f"Пропуск записи из-за отсутствия обязательных данных: {record}") continue # Сохраняем или обновляем запись в базе данных reservation, created = await sync_to_async(Reservation.objects.update_or_create)( external_id=booking_id, defaults={ "hotel": self.hotel, "status": status, "room_number": room_number, "check_in": arrival, "check_out": departure, }, ) if created: logger.info(f"Создана новая запись бронирования: {reservation}") print(reservation) else: logger.info(f"Обновлено существующее бронирование: {reservation}") processed_items += 1 except Exception as e: logger.error(f"Ошибка обработки бронирования {record.get('id')}: {e}") errors.append(str(e)) logger.info(f"Обработано бронирований: {processed_items}, ошибок: {len(errors)}") return {"processed_items": processed_items, "errors": errors} async def _fetch_data(self): """ Получает данные о бронированиях с API и возвращает их. """ logger.info("Начало процесса получения данных о бронированиях.") try: bookings = await self._fetch_paginated_data() # Получаем данные с пагинацией return bookings except Exception as e: logger.error(f"Ошибка в процессе получения данных: {e}") raise ValueError("Ошибка при получении данных о бронированиях") async def fetch_and_process_data(self): """Получение данных с API, обработка и сохранение в базу.""" logger.info("Начало загрузки данных с API") try: bookings = await self._fetch_paginated_data() report = await self._process_and_save_bookings(bookings) logger.info(f"Данные успешно обработаны. Отчет: {report}") return report except Exception as e: logger.error(f"Ошибка загрузки и обработки данных: {e}") raise