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 @@
Журнал синхронизации
-
- {% csrf_token %} -
-
-
-
-
- -
-
-
- -
-
-
-
-
-
-
-
-
-
Полученные записи:
-
-
-
- -
-
-
-
Обработанные записи:
-
-
-
- -
-
-
-
-
-
+ + + + +
+ +
@@ -63,10 +31,8 @@ - + - - @@ -74,14 +40,12 @@ - - - + {% 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 ]
# ОтельID бронирования Дата синхронизации Обработанные записиПолученные записиСоздан
{{ log.id }} {{ log.hotel.name }}{{ log.reservation_id }}{{ log.processed_records }}{{ log.recieved_records }} {{ log.created }}{{ log.processed_records }}
Нет журналов.Нет записей.