plugins development

This commit is contained in:
2024-12-10 10:32:46 +09:00
parent 7889117d6e
commit 8dce756a27
10 changed files with 352 additions and 50 deletions

View File

@@ -75,35 +75,48 @@ async def delete_hotel(update: Update, context):
await query.edit_message_text("Отель не найден.") await query.edit_message_text("Отель не найден.")
from pms_integration.manager import PMSIntegrationManager
from asgiref.sync import sync_to_async
from hotels.models import Hotel
# async def check_pms(update, context): # async def check_pms(update, context):
# query = update.callback_query # query = update.callback_query
# try: # try:
# # Логирование callback_data # # Получение hotel_id из callback_data
# hotel_id = query.data.split("_")[2] # hotel_id = query.data.split("_")[2]
# print(f"Selected hotel id: {hotel_id}") # print(f"Selected hotel id: {hotel_id}")
# context.user_data["selected_hotel"] = hotel_id # context.user_data["selected_hotel"] = hotel_id
# # Инициализация менеджера PMS # # Инициализация менеджера PMS
# pms_manager = PMSIntegrationManager(hotel_id=hotel_id) # pms_manager = PMSIntegrationManager(hotel_id=hotel_id)
# print(f"Loaded hotel: {pms_manager.hotel}") # print(f"Инициализация PMS менеджера для отеля ID: {hotel_id}")
# await pms_manager.load_hotel() # Асинхронная загрузка отеля
# pms_manager.load_plugin() # Загрузка плагина
# # Получение данных # # Загрузка данных отеля
# await pms_manager.load_hotel()
# print(f"Данные отеля загружены: {pms_manager.hotel}")
# # Загрузка плагина
# pms_manager.load_plugin()
# print(f"Плагин загружен: {pms_manager.plugin}")
# # Получение данных из PMS
# data = pms_manager.fetch_data() # data = pms_manager.fetch_data()
# print(f'PMS_managerПолучено записей: {len(data)}\n\n\n___') # print(f"Данные получены из PMS: {len(data)} записей")
# print(fанные {data}\n\n\n') # # print(f"Полные данные: {data}\n\n\n")
# # Логирование
# # Сохранение лога успешной интеграции
# await pms_manager.save_log("success", f"Успешная интеграция с PMS {pms_manager.pms_config.name}.") # await pms_manager.save_log("success", f"Успешная интеграция с PMS {pms_manager.pms_config.name}.")
# # Ответ пользователю
# await query.edit_message_text(f"Интеграция успешна! Получено {len(data)} записей.") # await query.edit_message_text(f"Интеграция успешна! Получено {len(data)} записей.")
# # Обработка данных и запись в БД
# await pms_manager.plugin._save_to_db(data, hotel_id=int(hotel_id))
# print(f"Данные успешно сохранены в базу данных.")
# except Exception as e: # except Exception as e:
# # Логирование ошибок # # Логирование ошибки
# print(f"Ошибка при выполнении check_pms: {e}")
# if 'pms_manager' in locals() and pms_manager.hotel: # if 'pms_manager' in locals() and pms_manager.hotel:
# await pms_manager.save_log("error", str(e)) # await pms_manager.save_log("error", str(e))
# await query.edit_message_text(f"❌ Ошибка: {e}") # await query.edit_message_text(f"❌ Ошибка: {e}")
@@ -112,45 +125,45 @@ async def check_pms(update, context):
query = update.callback_query query = update.callback_query
try: try:
# Получение hotel_id из callback_data # Получение ID отеля из callback_data
hotel_id = query.data.split("_")[2] hotel_id = query.data.split("_")[2]
print(f"Selected hotel id: {hotel_id}")
context.user_data["selected_hotel"] = hotel_id # Получение конфигурации отеля и PMS
hotel = await sync_to_async(Hotel.objects.select_related('pms').get)(id=hotel_id)
pms_config = hotel.pms
# Инициализация менеджера PMS if not pms_config:
await query.edit_message_text("PMS конфигурация не найдена.")
return
# Создаем экземпляр PMSIntegrationManager
pms_manager = PMSIntegrationManager(hotel_id=hotel_id) pms_manager = PMSIntegrationManager(hotel_id=hotel_id)
print(f"Инициализация PMS менеджера для отеля ID: {hotel_id}") await sync_to_async(pms_manager.load_hotel)()
await sync_to_async(pms_manager.load_plugin)()
# Загрузка данных отеля # Проверяем, какой способ интеграции использовать
await pms_manager.load_hotel() if hasattr(pms_manager.plugin, 'fetch_data'):
print(f"Данные отеля загружены: {pms_manager.hotel}") # Плагин поддерживает метод fetch_data
data = await sync_to_async(pms_manager.plugin.fetch_data)()
elif pms_config.api_url and pms_config.token:
# Используем прямой запрос к API
from pms_integration.api_client import APIClient
api_client = APIClient(base_url=pms_config.api_url, access_token=pms_config.token)
data = await sync_to_async(api_client.fetch_reservations)()
else:
# Если подходящий способ не найден
await query.edit_message_text("Подходящий способ интеграции с PMS не найден.")
return
# Загрузка плагина # Сохраняем данные в базу
pms_manager.load_plugin() from bot.utils.database import save_reservations
print(f"Плагин загружен: {pms_manager.plugin}") await sync_to_async(save_reservations)(data)
# Получение данных из PMS
data = pms_manager.fetch_data()
print(f"Данные получены из PMS: {len(data)} записей")
# print(f"Полные данные: {data}\n\n\n")
# Сохранение лога успешной интеграции
await pms_manager.save_log("success", f"Успешная интеграция с PMS {pms_manager.pms_config.name}.")
# Ответ пользователю
await query.edit_message_text(f"Интеграция успешна! Получено {len(data)} записей.")
# Обработка данных и запись в БД
await pms_manager.plugin._save_to_db(data, hotel_id=int(hotel_id))
print(f"Данные успешно сохранены в базу данных.")
# Уведомляем об успешной интеграции
await query.edit_message_text(f"Интеграция PMS {pms_config.name} завершена успешно.")
except Exception as e: except Exception as e:
# Логирование ошибки # Обрабатываем и логируем ошибки
print(f"Ошибка при выполнении check_pms: {e}") await query.edit_message_text(f"Ошибка: {str(e)}")
if 'pms_manager' in locals() and pms_manager.hotel:
await pms_manager.save_log("error", str(e))
await query.edit_message_text(f"❌ Ошибка: {e}")
async def setup_rooms(update: Update, context): async def setup_rooms(update: Update, context):
"""Настроить номера отеля.""" """Настроить номера отеля."""

View File

@@ -34,4 +34,22 @@ async def get_reservations(hotel_id, start_date=None, end_date=None):
query = query.filter(check_in__gte=start_date) query = query.filter(check_in__gte=start_date)
if end_date: if end_date:
query = query.filter(check_out__lte=end_date) query = query.filter(check_out__lte=end_date)
return await sync_to_async(list)(query.prefetch_related('guests')) return await sync_to_async(list)(query.prefetch_related('guests'))
def save_reservations(data):
"""
Сохранение данных бронирований в базу данных.
:param data: Список бронирований.
"""
for booking in data:
Reservation.objects.update_or_create(
external_id=booking['id'],
defaults={
'check_in': booking['begin_date'],
'check_out': booking['end_date'],
'amount': booking['amount'],
'notes': booking.get('notes', ''),
'guest_name': booking['client']['fio'],
'guest_phone': booking['client']['phone'],
},
)

View File

@@ -0,0 +1,116 @@
# Generated by Django 5.1.4 on 2024-12-09 09:30
import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='APIConfiguration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название API')),
('url', models.URLField(verbose_name='URL API')),
('token', models.CharField(blank=True, max_length=255, null=True, verbose_name='Токен')),
('username', models.CharField(blank=True, max_length=255, null=True, verbose_name='Логин')),
('password', models.CharField(blank=True, max_length=255, null=True, verbose_name='Пароль')),
('last_updated', models.DateTimeField(auto_now=True, verbose_name='Дата последнего обновления')),
],
options={
'verbose_name': 'Конфигурация API',
'verbose_name_plural': 'Конфигурации API',
},
),
migrations.CreateModel(
name='FraudLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('reservation_id', models.BigIntegerField(unique=True, verbose_name='ID бронирования')),
('guest_name', models.CharField(blank=True, max_length=255, null=True)),
('check_in_date', models.DateField()),
('detected_at', models.DateTimeField(auto_now_add=True)),
('message', models.TextField()),
],
options={
'verbose_name': 'Журнал мошенничества',
'verbose_name_plural': 'Журналы мошенничества',
},
),
migrations.CreateModel(
name='Guest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Имя гостя')),
('birthdate', models.DateField(blank=True, null=True, verbose_name='Дата рождения')),
('phone', models.CharField(blank=True, max_length=50, null=True, verbose_name='Телефон')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
],
options={
'verbose_name': 'Гость',
'verbose_name_plural': 'Гости',
},
),
migrations.CreateModel(
name='Reservation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('reservation_id', models.BigIntegerField(unique=True, verbose_name='ID бронирования')),
('room_number', models.CharField(max_length=50, verbose_name='Номер комнаты')),
('room_type', models.CharField(max_length=255, verbose_name='Тип комнаты')),
('check_in', models.DateTimeField(verbose_name='Дата заезда')),
('check_out', models.DateTimeField(verbose_name='Дата выезда')),
('status', models.CharField(max_length=50, verbose_name='Статус')),
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Цена')),
('discount', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Скидка')),
],
options={
'verbose_name': 'Бронирование',
'verbose_name_plural': 'Бронирования',
},
),
migrations.CreateModel(
name='UserHotel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'verbose_name': 'Пользователь отеля',
'verbose_name_plural': 'Пользователи отелей',
},
),
migrations.CreateModel(
name='APIRequestLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('request_time', models.DateTimeField(auto_now_add=True, verbose_name='Время запроса')),
('response_status', models.IntegerField(validators=[django.core.validators.MinValueValidator(100), django.core.validators.MaxValueValidator(599)], verbose_name='HTTP статус ответа')),
('response_data', models.JSONField(blank=True, null=True, verbose_name='Данные ответа')),
('api', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.apiconfiguration', verbose_name='API')),
],
options={
'verbose_name': 'Журнал запросов API',
'verbose_name_plural': 'Журналы запросов API',
},
),
migrations.CreateModel(
name='Hotel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название отеля')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')),
('api', models.OneToOneField(blank=True, help_text='API, связанный с этим отелем.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.apiconfiguration', verbose_name='API')),
],
options={
'verbose_name': 'Отель',
'verbose_name_plural': 'Отели',
},
),
]

View File

@@ -0,0 +1,42 @@
# Generated by Django 5.1.4 on 2024-12-09 09:30
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('hotels', '0001_initial'),
('pms_integration', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='hotel',
name='pms',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pms_integration.pmsconfiguration', verbose_name='PMS система'),
),
migrations.AddField(
model_name='fraudlog',
name='hotel',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frauds', to='hotels.hotel'),
),
migrations.AddField(
model_name='reservation',
name='hotel',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'),
),
migrations.AddField(
model_name='guest',
name='reservation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guests', to='hotels.reservation', verbose_name='Бронирование'),
),
migrations.AddField(
model_name='userhotel',
name='hotel',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hotel_users', to='hotels.hotel', verbose_name='Отель'),
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 5.1.4 on 2024-12-09 09:30
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('hotels', '0002_initial'),
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_hotels', to='users.user', verbose_name='Пользователь'),
),
migrations.AddIndex(
model_name='apirequestlog',
index=models.Index(fields=['api'], name='hotels_apir_api_id_686bb0_idx'),
),
migrations.AddIndex(
model_name='apirequestlog',
index=models.Index(fields=['request_time'], name='hotels_apir_request_f65147_idx'),
),
]

View File

View File

@@ -118,13 +118,18 @@ class Guest(models.Model):
verbose_name = "Гость" verbose_name = "Гость"
verbose_name_plural = "Гости" verbose_name_plural = "Гости"
class FraudLog(models.Model): class FraudLog(models.Model):
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name="frauds") hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name="frauds")
reservation_id = models.CharField(max_length=255) reservation_id = models.BigIntegerField(unique=True, verbose_name="ID бронирования")
guest_name = models.CharField(max_length=255, null=True, blank=True) guest_name = models.CharField(max_length=255, null=True, blank=True)
check_in_date = models.DateField() check_in_date = models.DateField()
detected_at = models.DateTimeField(auto_now_add=True) detected_at = models.DateTimeField(auto_now_add=True)
message = models.TextField() message = models.TextField()
def __str__(self): def __str__(self):
return f"FRAUD: {self.guest_name} ({self.check_in_date})" return f"FRAUD: {self.guest_name} ({self.check_in_date})"
class Meta:
verbose_name = "Журнал мошенничества"
verbose_name_plural = "Журналы мошенничества"

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2024-12-09 04:50 # Generated by Django 5.1.4 on 2024-12-09 09:30
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
@@ -9,7 +9,7 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('hotels', '__first__'), ('hotels', '0001_initial'),
] ]
operations = [ operations = [

View File

@@ -0,0 +1,78 @@
import hashlib
import requests
import json
from datetime import datetime
from hotels.models import Reservation
from pms_integration.plugins.base_plugin import BasePMSPlugin
class RealtyCalendarPlugin(BasePMSPlugin):
"""
Плагин для взаимодействия с RealtyCalendar.
"""
def __init__(self, config):
super().__init__(config)
self.public_key = config.token # Используем `token` как публичный ключ
self.private_key = config.password # Используем `password` как приватный ключ
self.base_url = config.url
def generate_sign(self, params):
"""
Генерация подписи запроса.
:param params: Параметры запроса.
:return: Подпись.
"""
sorted_keys = sorted(params.keys())
data_string = ''.join(f"{key}={params[key]}" for key in sorted_keys)
sign_string = f"{data_string}{self.private_key}"
return hashlib.md5(sign_string.encode('utf-8')).hexdigest()
def fetch_data(self, start_date=None, end_date=None):
"""
Получение данных из RealtyCalendar.
:param start_date: Начальная дата (формат YYYY-MM-DD).
:param end_date: Конечная дата (формат YYYY-MM-DD).
:return: Список данных бронирования.
"""
if not start_date:
start_date = datetime.now().strftime('%Y-%m-%d')
if not end_date:
end_date = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d')
params = {
'begin_date': start_date,
'end_date': end_date,
}
params['sign'] = self.generate_sign(params)
url = f"{self.base_url}/bookings/{self.public_key}/"
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
response = requests.post(url, json=params, headers=headers)
response.raise_for_status()
data = response.json()
return data.get('bookings', [])
@staticmethod
def save_data(bookings):
"""
Сохранение данных бронирования в базу данных.
:param bookings: Список бронирований.
"""
for booking in bookings:
Reservation.objects.update_or_create(
external_id=booking['id'],
defaults={
'check_in': booking['begin_date'],
'check_out': booking['end_date'],
'amount': booking['amount'],
'notes': booking.get('notes', ''),
'guest_name': booking['client']['fio'],
'guest_phone': booking['client']['phone'],
},
)

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.1.4 on 2024-12-09 04:50 # Generated by Django 5.1.4 on 2024-12-09 09:30
import django.contrib.auth.models import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators