@@ -4,20 +4,51 @@ 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
from pms_integration . models import PMSConfiguration
from hotels . models import Hotel , Reservation
import logging
logger = CustomLogger ( name = " BnovoPMS Plugin " , log_level = " DEBUG " ) . get_logger ( )
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 . WARNING ) # Меняем уровень логов для консоли
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 , config ) :
super ( ) . __init__ ( config )
self . api_url = config . url . rstrip ( " / " )
self . username = config . username
self . password = config . password
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 :
@@ -35,27 +66,26 @@ class BnovoPMSPlugin(BasePMSPlugin):
" timezone " : " UTC "
}
async def _get_stored_token ( self , hotel_id ):
""" Получение токена из базы данных для конкретного отеля. """
async def _get_stored_token ( self ) :
""" Получение токена из конфигурации PMS отеля. """
try :
logger . debug ( f " Попытка получения токена из базы данных для отеля: { hotel_ id } . " )
config = await sync_to_async ( PMSConfiguration . objects . get ) ( plugin_name = " bnovo " , hotel_id = hotel_id )
logger . debug ( f " Токен из базы данных: { config . token} " )
return config . token
except PMSConfiguration . DoesNotExist :
logger . warning ( f " Токен отсутствует в базе данных для отеля: { hotel_ id } . " )
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 , hotel_id ):
""" Сохраняет токен (SID) в базу данных для конкретного отеля. """
async def _save_token_to_db ( self , sid ) :
""" Сохраняет токен (SID) в конфигурации PMS отеля. """
try :
logger . debug ( f " Сохранение токена в базу данных для отеля { hotel_ id } : { sid } " )
await sync_to_async ( PMSConfiguration . objects . update_or_create ) (
plugin_name = " bnovo " , hotel_id = hotel_id , defaults = { " token " : sid }
)
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 " Ошибка сохранения токена в базу данных для отеля { hotel_ id } : { e } " )
logger . error ( f " Ошибка сохранения токена для отеля { self . hotel. id } : { e } " )
def _get_auth_headers ( self ) :
""" Создает заголовки авторизации. """
@@ -69,8 +99,8 @@ class BnovoPMSPlugin(BasePMSPlugin):
logger . debug ( f " Добавлен токен в заголовки: { self . token } " )
return headers
async def _fetch_session ( self , hotel_id ):
""" Получение нового токена (SID) через запрос для конкретного отеля . """
async def _fetch_session ( self ) :
""" Получение нового токена (SID) через запрос. """
url = f " { self . api_url } / "
payload = { " username " : self . username , " password " : self . password }
headers = self . _get_auth_headers ( )
@@ -85,7 +115,7 @@ class BnovoPMSPlugin(BasePMSPlugin):
if sid :
self . token = sid
logger . debug ( f " Получен новый SID: { sid } " )
await self . _save_token_to_db ( sid , hotel_id )
await self . _save_token_to_db ( sid )
else :
logger . error ( " Н е удалось извлечь SID из ответа." )
raise ValueError ( " Н е удалось извлечь SID из ответа." )
@@ -93,14 +123,14 @@ class BnovoPMSPlugin(BasePMSPlugin):
logger . error ( f " Ошибка авторизации: { response . status_code } , { response . text } " )
raise ValueError ( f " Ошибка авторизации: { response . status_code } , { response . text } " )
async def _fetch_account_data ( self , hotel_id ):
""" Получение данных аккаунта через эндпоинт /account/current для конкретного отеля . """
logger . info ( f " Начало получения данных аккаунта для отеля: { hotel_ id } . " )
self . token = await self . _get_stored_token ( hotel_id )
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 ( hotel_id )
await self . _fetch_session ( )
url = f " { self . api_url } /account/current "
headers = self . _get_auth_headers ( )
@@ -114,29 +144,29 @@ class BnovoPMSPlugin(BasePMSPlugin):
try :
account_data = response . json ( )
logger . debug ( f " Полученные данные аккаунта: { json . dumps ( account_data , indent = 2 ) } " )
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 , hotel_id ):
async def _ fetch_and_log_account_data( self ) :
""" Вызов метода _fetch_account_data и вывод результата в лог. """
logger . info ( f " Запуск получения и логирования данных аккаунта для отеля: { hotel_ id } . " )
logger . info ( f " Запуск получения и логирования данных аккаунта для отеля { self . hotel. id } . " )
try :
account_data = await self . _fetch_account_data ( hotel_id )
logger . info ( f " Успешно полученные данные аккаунта: { json . dumps ( account_data , indent = 2 ) } " )
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 , hotel_id ):
""" Получение данных аккаунта и бронирований для конкретного отеля . """
logger . info ( f " Запуск процесса получения данных аккаунта и бронирований для отеля: { hotel_ id } . " )
async def _ fetch_data_with_account_info( self ) :
""" Получение данных аккаунта и бронирований. """
logger . info ( f " Запуск процесса получения данных аккаунта и бронирований для отеля { self . hotel. id } . " )
try :
account_data = await self . fetch_and_log_account_data ( hotel_id )
account_data = await self . fetch_and_log_account_data ( )
logger . info ( " Данные аккаунта успешно получены, продолжение с бронированиями. " )
await self . __fetch_data ( )
except Exception as e :
@@ -144,21 +174,19 @@ class BnovoPMSPlugin(BasePMSPlugin):
async def _fetch_data ( self ) :
""" Получение данных о бронированиях с помощью эндпоинта /dashboard. """
# Проверяем наличие токена в базе данных
logger . info ( " Начало процесса получения данных. " )
self . token = await self . _get_stored_token ( )
logger . info ( " Начало процесса получения данных о бронированиях. " )
if not self . token :
logger . info ( " Токен отсутствует, выполняем авторизацию. " )
await self. _fetch_session ( )
accounts = await self . _fetch_account_data ( )
print ( f ' \n ------ \n ACCOUNTS: { accounts } \n ------- \n ' )
# # Вызов функции получения данных аккаунта
# try:
# account_data = await self. _fetch_and_log_account_data( )
# logger.info(f"Данные аккаунта успешно получены:")
# except Exception as e:
# logger.error(f"Ошибка получения данных аккаунта: {e}" )
# raise
url = f " { self . api_url } /dashboard "
now = datetime . now ( )
create_from = ( now - timedelta ( days = 90 ) ) . strftime ( " %d . % m. % Y " )
create_from = ( now - timedelta ( days = 1 ) ) . strftime ( " %d . % m. % Y " )
create_to = now . strftime ( " %d . % m. % Y " )
params = {
@@ -171,69 +199,83 @@ class BnovoPMSPlugin(BasePMSPlugin):
}
headers = self . _get_auth_headers ( )
logger . debug ( f " Начальный запрос к { url } с параметрами: { json . dumps ( params , indent = 2 ) } " )
all_bookings = [ ]
while True :
logger . debug ( f " Выполнение з апроса к { url } " )
response = requests . get ( url , headers = headers , params = params )
logger . debug ( f " Ответ запроса: статус { response . status_code } , тело { response . text } " )
if response . status_code != 200 :
logger . error ( f " Ошибка при запросе: { response . status_code } , { response . text } " )
raise ValueError ( " Ошибка запроса к /dashboard " )
logger . debug ( f " З апрос к { url } с параметрами: { json . dumps ( params , indent = 2 ) } ")
try :
response = requests . get ( url , headers = headers , params = params , allow_redirects = False )
if response . status_code == 302 :
logger . warning ( " Получен код 302. Перенаправление. " )
redirected_url = response . headers . get ( " Location " )
if redirected_url :
logger . debug ( f " Перенаправление на { redirected_url } " )
url = redirected_url
continue
else :
logger . error ( " Ответ с кодом 302 не содержит заголовка Location. " )
raise ValueError ( " Перенаправление без указанного URL. " )
if response . status_code != 200 :
logger . error ( f " Ошибка при запросе: { response . status_code } , { response . text } " )
raise ValueError ( " Ошибка запроса к /dashboard " )
data = response . json ( )
logger . debug ( f " Полученные данные: { json . dumps ( data , indent = 2 ) } " )
bookings = data . get ( " bookings " , [ ] )
rooms = data . get ( " rooms " , [ ] )
logger . debug ( f ' bookings: { bookings } \n rooms: { rooms } ' )
all_bookings . extend ( bookings )
logger . info ( f " Получено бронирований: { len ( bookings ) } . В с е г о : { len ( all_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 json . JSONDecodeError as e :
logger . error ( f " Ошибка декодирования JSON: { e } " )
logger . error ( f " Ошибка декодирования JSON: { e } . Ответ: { response . text } ")
raise ValueError ( f " Ошибка декодирования JSON: { e } " )
bookings = data . get ( " bookings " , [ ] )
all_bookings . extend ( bookings )
logger . info ( f " Получено бронирований за запрос: { len ( bookings ) } . В с е г о : { len ( all_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
# Сопоставляем бронирования с существующими записями
for booking in all_bookings :
booking_id = booking . get ( " id " )
hotel _id = booking . get ( " hotel_ id" )
if not booking_id or not hotel_id:
logger . warning ( " У бронирования отсутствует id или hotel_id. Пропуск." )
continue
try :
booking _id = booking . get ( " id " )
hotel_id = booking . get ( " hotel_id " )
if hotel_id != str ( self . config . hotel. external_id_pms ) :
logger . debug ( f " Бронирование { booking_id } не относится к отелю { self . config . hotel. external_id_pms } . Пропуск. " )
continue
if hotel_id != str ( self . hotel . external_id_pms ) :
logger . debug ( f " Бронирование { booking_id } не относится к отелю { self . hotel . external_id_pms } . Пропуск. " )
continue
reservation , created = await sync_to_async ( Reservation . objects . update_or_create ) (
external_id = booking_id ,
defaults = {
" hotel " : self . config . hotel,
" status " : booking . get ( " status_name " ) ,
" create_date " : booking . get ( " create_date " ) ,
" arrival " : booking . get ( " arrival " ) ,
" departure " : booking . get ( " departure " ) ,
" room_type " : booking . get ( " initial_room_type_name " ) ,
" data " : booking
}
)
reservation , created = await sync_to_async ( Reservation . objects . update_or_create ) (
external_id = booking_id ,
defaults = {
" hotel " : self . hotel , # Объект модели Hotel
" status " : booking . get ( " status_name " ) , # Статус бронирования
" room_number " : booking . get ( " current_room " ) , # Номер комнаты (исправлено с create_date)
" check_in " : booking . get ( " arrival " ) , # Дата заезда
" check_out " : booking . get ( " departure " ) , # Дата выезда
" room_type " : booking . get ( " initial_room_type_name " ) # Тип комнаты
}
)
if created :
logger . info ( f " Создана новая запись бронирования: { reservation } " )
else :
logger . info ( f " Обновлено существующее бронирование: { reservation } " )
if created :
logger . info ( f " Создана новая запись бронирования: { reservation } " )
else :
logger . info ( f " Обновлено существующее бронирование: { reservation } " )
except Exception as e :
logger . error ( f " Ошибка обработки бронирования { booking . get ( ' id ' ) } : { e } " )
logger . info ( f " В с е бронирования получены и обработаны. Итоговое количество: { len ( all_bookings ) } " )
return all_bookings