diff --git a/antifroud/admin.py b/antifroud/admin.py
index e8c052f6..d91c877c 100644
--- a/antifroud/admin.py
+++ b/antifroud/admin.py
@@ -112,7 +112,7 @@ class ExternalDBSettingsAdmin(admin.ModelAdmin):
@admin.register(UserActivityLog)
class UserActivityLogAdmin(admin.ModelAdmin):
list_display = ("id", 'get_location',"formatted_timestamp", "date_time", "page_id", "url_parameters", "page_url" ,"created", "page_title", "type", "hits")
- search_fields = ("page_title", "url_parameters")
+ search_fields = ("page_title", "url_parameters", "page_title")
list_filter = ("page_title", "created")
readonly_fields = ("created", "timestamp")
@@ -204,16 +204,30 @@ class UserActivityLogAdmin(admin.ModelAdmin):
@admin.register(SyncLog)
class SyncLogAdmin(admin.ModelAdmin):
- change_list_template = "antifroud/admin/sync_log.html"
- list_display =['id', 'hotel', 'created', 'recieved_records', 'processed_records']
- search_fields = ['id', 'hotel', 'created', 'recieved_records', 'processed_records']
- list_filter = ['id', 'hotel', 'created', 'recieved_records', 'processed_records']
+ change_list_template = "antifroud/admin/sync_log.html" # Путь к вашему кастомному шаблону
+ list_display = ['id', 'hotel', 'created', 'recieved_records', 'processed_records']
+ search_fields = ['id', 'hotel__name', 'recieved_records', 'processed_records']
+ list_filter = ['hotel', 'created']
- class Meta:
- model = SyncLog
- fields = ['hotel', 'recieved_records', 'processed_records']
-
+ def changelist_view(self, request, extra_context=None):
+ """
+ Добавляет фильтрацию по отелям в шаблон.
+ """
+ extra_context = extra_context or {}
+ # Получаем выбранный фильтр отеля из GET-параметров
+ hotel_id = request.GET.get('hotel')
+ hotels = Hotel.objects.all()
+ sync_logs = SyncLog.objects.all()
+
+ if hotel_id:
+ sync_logs = sync_logs.filter(hotel_id=hotel_id)
+
+ extra_context['sync_logs'] = sync_logs
+ extra_context['hotels'] = hotels
+ extra_context['selected_hotel'] = hotel_id # Чтобы отобразить выбранный отель
+
+ return super().changelist_view(request, extra_context=extra_context)
@admin.register(ViolationLog)
class ViolationLogAdmin(admin.ModelAdmin):
list_display = ['id', 'hotel', 'room_number' , 'hits', 'created_at', 'violation_type', 'violation_details', 'detected_at']
diff --git a/antifroud/templates/antifroud/admin/sync_log.html b/antifroud/templates/antifroud/admin/sync_log.html
index 895ed72b..db7dfeab 100644
--- a/antifroud/templates/antifroud/admin/sync_log.html
+++ b/antifroud/templates/antifroud/admin/sync_log.html
@@ -1,7 +1,6 @@
{% extends "admin/change_list.html" %}
{% block content %}
-
@@ -9,53 +8,22 @@
Журнал синхронизации
-
+
+
@@ -63,10 +31,8 @@
| # |
Отель |
- ID бронирования |
+ Дата синхронизации |
Обработанные записи |
- Полученные записи |
- Создан |
@@ -74,14 +40,12 @@
| {{ log.id }} |
{{ log.hotel.name }} |
- {{ log.reservation_id }} |
- {{ log.processed_records }} |
- {{ log.recieved_records }} |
{{ log.created }} |
+ {{ log.processed_records }} |
{% empty %}
- | Нет журналов. |
+ Нет записей. |
{% endfor %}
@@ -93,4 +57,4 @@
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/pms_integration/plugins/realtycalendar_pms.py b/pms_integration/plugins/realtycalendar_pms.py
index 7f80ae46..f29fa2cc 100644
--- a/pms_integration/plugins/realtycalendar_pms.py
+++ b/pms_integration/plugins/realtycalendar_pms.py
@@ -6,8 +6,9 @@ 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
class RealtyCalendarPlugin(BasePMSPlugin):
- """Плагин для импорта данных из системы RealtyCalendar."""
def __init__(self, config):
super().__init__(config)
self.public_key = config.public_key
@@ -72,145 +73,107 @@ class RealtyCalendarPlugin(BasePMSPlugin):
"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"),
}
-
- self.logger.debug(f"Даты выборки: {data}")
-
- # Генерация подписи
data["sign"] = self._generate_sign(data)
- # Отправляем запрос
- self.logger.debug(f"URL запроса: {base_url}")
- self.logger.debug(f"Заголовки: {headers}")
- self.logger.debug(f"Данные запроса: {data}")
-
response = requests.post(url=base_url, headers=headers, json=data)
- self.logger.debug(f"Запрос: {response}")
self.logger.debug(f"Статус ответа: {response.status_code}")
- # self.logger.debug(f"Ответ: {response.text}")
- if response.status_code == 200:
- try:
- response_data = response.json()
- self.logger.debug(f"Тип данных ответа: {type(response_data)}")
- if not isinstance(response_data, dict) or "bookings" not in response_data:
- raise ValueError(f"Неожиданная структура ответа: {response_data}")
- bookings = response_data.get("bookings", [])
- # self.logger.debug(f"Полученные данные бронирований: {bookings}")
- except json.JSONDecodeError as e:
- self.logger.error(f"Ошибка декодирования JSON: {e}")
- raise ValueError("Ошибка декодирования JSON ответа.")
+ if response.status_code != 200:
+ self.logger.error(f"Ошибка API: {response.status_code}, {response.text}")
+ raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}")
- # Фильтрация данных
- filtered_data = [
- {
- "id": item.get("id"),
- "begin_date": item.get("begin_date"),
- "end_date": item.get("end_date"),
- "amount": item.get("amount"),
- "status": item.get("status"),
- "is_delete": item.get("is_delete"),
- "apartment_id": item.get("apartment_id"),
- "prepayment": item.get("prepayment"),
- "deposit": item.get("deposit"),
- "source": item.get("source"),
- "notes": item.get("notes"),
- }
- for item in bookings
- if isinstance(item, dict) and item.get("status") in ["booked", "request"]
- ]
- self.logger.debug(f"Отфильтрованные данные: {type(filtered_data)}")
+ try:
+ response_data = response.json()
+ bookings = response_data.get("bookings", [])
+ if not isinstance(bookings, list):
+ raise ValueError(f"Ожидался список, но получен {type(bookings)}")
+ except Exception as e:
+ self.logger.error(f"Ошибка обработки ответа API: {e}")
+ raise
- for item in filtered_data:
- self.logger.debug(f"Данные бронирования: {item}")
- await self._save_to_db(item)
-
- from django.utils import timezone
+ # Получаем глобальные настройки отеля
+ hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config)
+ hotel_tz = hotel.timezone
+ try:
+ hotel_settings = await sync_to_async(GlobalHotelSettings.objects.first)()
+ check_in_time = hotel_settings.check_in_time.strftime("%H:%M:%S")
+ check_out_time = hotel_settings.check_out_time.strftime("%H:%M:%S")
+ except AttributeError:
+ # Используем значения по умолчанию, если настроек нет
+ check_in_time = "14:00:00"
+ check_out_time = "12:00:00"
+
+ filtered_data = [
+ {
+ 'reservation_id': item.get('id'),
+ 'checkin': timezone.make_aware(
+ datetime.strptime(
+ f"{item.get('begin_date')} {check_in_time}",
+ "%Y-%m-%d %H:%M:%S"
+ )
+ ),
+ 'checkout': timezone.make_aware(
+ datetime.strptime(
+ f"{item.get('end_date')} {check_out_time}",
+ "%Y-%m-%d %H:%M:%S"
+ )
+ ),
+ 'room_number': item.get('apartment_id'),
+ 'room_type': item.get('notes', 'Описание отсутствует'),
+ 'status': item.get('status')
+ }
+ for item in bookings
+ if isinstance(item, dict) and item.get("status") in ["booked", "request"]
+ ]
+
+ await self._save_to_db(filtered_data)
async def _save_to_db(self, data):
- from django.utils import timezone
"""
Сохраняет данные в БД (например, информацию о номере).
"""
- try:
- # Проверяем общее количество записей для обработки
- if not isinstance(data, list):
- self.logger.error(f"Ожидался список записей, но получен {type(data).__name__}")
- return
+ if not isinstance(data, list):
+ self.logger.error(f"Ожидался список записей, но получен {type(data).__name__}")
+ return
- total_records = len(data)
- self.logger.info(f"Общее количество записей для обработки: {total_records}")
+ for index, item in enumerate(data, start=1):
+ 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
- for index, item in enumerate(data, start=1):
- try:
- self.logger.info(f"Обработка записи {index}/{total_records}")
+ existing_reservation = await sync_to_async(Reservation.objects.filter)(reservation_id=reservation_id)
+ existing_reservation = await sync_to_async(existing_reservation.first)()
- # Проверка типа данных
- if not isinstance(item, dict):
- self.logger.error(f"Пропущена запись {index}/{total_records}: ожидался dict, но получен {type(item).__name__}")
- continue
+ defaults = {
+ 'room_number': item['room_number'],
+ 'room_type': item['room_type'],
+ 'check_in': item['checkin'],
+ 'check_out': item['checkout'],
+ 'status': item['status'],
+ 'hotel': hotel
+ }
- # Получаем отель по настройкам PMS
- hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config)
- self.logger.debug(f"Отель найден: {hotel.name}")
-
- # Проверяем, существует ли уже резервация с таким внешним ID
- reservation_id = item.get('id')
- if not reservation_id:
- self.logger.error(f"Пропущена запись {index}/{total_records}: отсутствует 'id' в данных.")
- continue
-
- # Преобразуем даты в "aware" объекты
- try:
- check_in = timezone.make_aware(datetime.strptime(item.get('begin_date'), "%Y-%m-%d"))
- check_out = timezone.make_aware(datetime.strptime(item.get('end_date'), "%Y-%m-%d"))
- except Exception as e:
- self.logger.error(f"Ошибка преобразования дат для записи {index}/{total_records}: {e}")
- continue
-
- existing_reservation = await sync_to_async(Reservation.objects.filter)(reservation_id=reservation_id)
-
- # Теперь вызываем .first() после асинхронного вызова
- existing_reservation = await sync_to_async(existing_reservation.first)()
-
- if existing_reservation:
- self.logger.debug(f"Резервация {reservation_id} уже существует. Обновляем...")
- await sync_to_async(Reservation.objects.update_or_create)(
- reservation_id=reservation_id,
- defaults={
- 'room_number': item.get('apartment_id'),
- 'room_type': 'Описание отсутствует',
- 'check_in': check_in,
- 'check_out': check_out,
- 'status': item.get('status'),
- 'hotel': hotel
- }
- )
- self.logger.debug(f"Резервация обновлена.")
- else:
- self.logger.debug(f"Резервация не найдена, создаем новую...")
- reservation = await sync_to_async(Reservation.objects.create)(
- reservation_id=reservation_id,
- room_number=item.get('apartment_id'),
- room_type='Описание отсутствует',
- check_in=check_in,
- check_out=check_out,
- status=item.get('status'),
- hotel=hotel
- )
- self.logger.debug(f"Новая резервация создана с ID: {reservation.reservation_id}")
-
- except Exception as e:
- self.logger.error(f"Ошибка при обработке записи {index}/{total_records}: {e}")
-
- except Exception as e:
- self.logger.error(f"Ошибка обработки данных в _save_to_db: {e}")
+ 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}")
def validate_plugin(self):
"""
diff --git a/touchh/settings.py b/touchh/settings.py
index bcd4a0ef..895986e4 100644
--- a/touchh/settings.py
+++ b/touchh/settings.py
@@ -34,10 +34,10 @@ SECRET_KEY = 'django-insecure-l_8uu8#p*^zf)9zry80)6u+!+2g1a4tg!wx7@^!uw(+^axyh&h
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = ['0.0.0.0', '192.168.219.140', '127.0.0.1', 'localhost', '192.168.219.114', '588a-182-226-158-253.ngrok-free.app', '*.ngrok-free.app']
+ALLOWED_HOSTS = ['0.0.0.0', '192.168.219.140', '127.0.0.1', 'localhost', '192.168.219.114', '8f6e-182-226-158-253.ngrok-free.app', '*.ngrok-free.app']
CSRF_TRUSTED_ORIGINS = [
- 'http://588a-182-226-158-253.ngrok-free.app',
+ 'https://8f6e-182-226-158-253.ngrok-free.app',
'https://*.ngrok-free.app', # Это подойдет для любых URL, связанных с ngrok
]