Files
Touchh/pms_integration/plugins/ecvi_pms.py
2025-02-26 22:29:39 +09:00

333 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# # ecvi_pms.py
# import logging
# 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
# class EcviPMSPlugin(BasePMSPlugin):
# """
# Плагин для интеграции с PMS Ecvi.
# """
# 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.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):
# headers = {"Content-Type": "application/json"}
# data = {"token": self.token}
# try:
# 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}")
# 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 = 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'],
# defaults={
# 'room_number': item['room_name'],
# 'room_type': item['room_type'],
# 'check_in': checkin,
# 'check_out': checkout,
# 'status': item['occupancy'],
# 'hotel': self.hotel,
# }
# )
# 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
import os
import json
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)
self.hotel = hotel
if not self.hotel.pms:
raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.")
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)
# Директория для сохранения JSON-файлов
self.data_dir = "var/data/ecvi"
os.makedirs(self.data_dir, exist_ok=True)
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:
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}")
# Сохраняем весь ответ API в файл для анализа
file_name = f"ecvi_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
file_path = os.path.join(self.data_dir, file_name)
with open(file_path, "w", encoding="utf-8") as f:
json.dump(response_data, f, ensure_ascii=False, indent=4)
self.logger.info(f"API-ответ сохранен в файл: {file_path}")
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"]
unix_epoch = datetime(1970, 1, 1, 0, 0, 0)
valid_reservations = []
print(data)
for item in data:
try:
checkin = item.get('checkin')
checkout = item.get('checkout')
# Фильтруем записи с некорректными датами
if checkin in [None, "0000-00-00 00:00:00", "1970-01-01 00:00:00", 0] or \
checkout in [None, "0000-00-00 00:00:00", "1970-01-01 00:00:00", 0]:
self.logger.warning(f"Игнорируется запись с некорректной датой: {item}")
continue
checkin = self._parse_date(checkin, date_formats)
checkout = self._parse_date(checkout, date_formats)
# Проверяем на Unix epoch
if checkin == unix_epoch or checkout == unix_epoch:
self.logger.warning(f"Игнорируется запись с Unix epoch датой: {item}")
continue
# Проверяем timestamp
if checkin.timestamp() == 0 or checkout.timestamp() == 0:
self.logger.warning(f"Игнорируется запись с timestamp = 0: {item}")
continue
valid_reservations.append(item)
except Exception as e:
self.logger.error(f"Ошибка обработки записи: {e}")
errors.append(str(e))
# Логируем количество отфильтрованных записей
self.logger.info(f"Обработано бронирований: {len(valid_reservations)}")
# Сохранение валидных бронирований в JSON для проверки
valid_file_name = f"valid_reservations_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
valid_file_path = os.path.join(self.data_dir, valid_file_name)
with open(valid_file_path, "w", encoding="utf-8") as f:
json.dump(valid_reservations, f, ensure_ascii=False, indent=4)
self.logger.info(f"Валидные бронирования сохранены в файл: {valid_file_path}")
# Сохранение данных в БД
for item in valid_reservations:
try:
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': self._parse_date(item['checkin'], date_formats),
'check_out': self._parse_date(item['checkout'], date_formats),
'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