remote changes
This commit is contained in:
@@ -1,26 +1,149 @@
|
||||
# import json
|
||||
# from datetime import timedelta
|
||||
# from django.utils import timezone
|
||||
# from django.db.models import Q
|
||||
# from hotels.models import Reservation, Hotel
|
||||
# from .models import UserActivityLog, RoomDiscrepancy
|
||||
# from touchh.utils.log import CustomLogger
|
||||
|
||||
# # Настройка логирования
|
||||
# logger = CustomLogger(__name__).get_logger()
|
||||
|
||||
# class ReservationChecker:
|
||||
# """
|
||||
# Класс для проверки несоответствий между бронированиями и логами заселения.
|
||||
# """
|
||||
|
||||
# def __init__(self):
|
||||
# self.checkin_diff_hours = 3
|
||||
|
||||
# def log_info(self, message):
|
||||
# logger.info(message)
|
||||
|
||||
# def log_warning(self, message):
|
||||
# logger.warning(message)
|
||||
|
||||
# def log_error(self, message):
|
||||
# logger.error(message)
|
||||
|
||||
# def run_check(self):
|
||||
# """Запуск проверки фродовых событий."""
|
||||
# self.log_info("Запуск проверки фродовых данных.")
|
||||
# try:
|
||||
# check_in_diff = timedelta(hours=self.checkin_diff_hours)
|
||||
|
||||
# # Кэшируем отели в словарь для быстрого доступа
|
||||
# hotels_map = {hotel.hotel_id: hotel for hotel in Hotel.objects.all()}
|
||||
|
||||
# # Загружаем бронирования и активности пользователей
|
||||
# user_logs = UserActivityLog.objects.filter(fraud_checked=False)
|
||||
# reservations = Reservation.objects.filter(fraud_checked=False).select_related('hotel')
|
||||
|
||||
# # Преобразуем бронирования в словарь для быстрого поиска
|
||||
# reservations_map = {
|
||||
# (res.hotel.hotel_id, res.room_number): res for res in reservations
|
||||
# }
|
||||
|
||||
# violations = []
|
||||
# missing_reservations = set(reservations) # Сет для поиска пропавших бронирований
|
||||
|
||||
# for user_log in user_logs:
|
||||
# try:
|
||||
# params = json.loads(user_log.url_parameters.replace("'", '"')) if user_log.url_parameters else {}
|
||||
# hotel_id = params.get('utm_content')
|
||||
# room = params.get('utm_term')
|
||||
|
||||
# if not hotel_id or not room:
|
||||
# continue # Пропускаем записи без нужных параметров
|
||||
|
||||
# key = (hotel_id, room)
|
||||
# reserv = reservations_map.get(key)
|
||||
|
||||
# discrepancy_type = None
|
||||
|
||||
# if reserv:
|
||||
# if reserv in missing_reservations:
|
||||
# missing_reservations.remove(reserv)
|
||||
|
||||
# if user_log.date_time < reserv.check_in:
|
||||
# discrepancy_type = 'early'
|
||||
# elif user_log.date_time > reserv.check_in + check_in_diff:
|
||||
# discrepancy_type = 'late'
|
||||
# else:
|
||||
# discrepancy_type = 'no_booking'
|
||||
|
||||
# if discrepancy_type:
|
||||
# violations.append(RoomDiscrepancy(
|
||||
# hotel=hotels_map.get(hotel_id),
|
||||
# room_number=room,
|
||||
# discrepancy_type=discrepancy_type,
|
||||
# booking_id=reserv.reservation_id if reserv else None,
|
||||
# check_in_date_expected=reserv.check_in if reserv else None,
|
||||
# check_in_date_actual=user_log.date_time,
|
||||
# ))
|
||||
|
||||
# user_log.fraud_checked = True # Отмечаем логи как проверенные
|
||||
|
||||
# except json.JSONDecodeError:
|
||||
# self.log_error(f"Ошибка декодирования JSON в URL-параметрах: {user_log.url_parameters}")
|
||||
# except Exception as e:
|
||||
# self.log_error(f"Ошибка при обработке логов: {e}")
|
||||
|
||||
# # Добавляем пропущенные бронирования
|
||||
# for miss_reserv in missing_reservations:
|
||||
# violations.append(RoomDiscrepancy(
|
||||
# hotel=miss_reserv.hotel,
|
||||
# room_number=miss_reserv.room_number,
|
||||
# discrepancy_type='missed',
|
||||
# booking_id=miss_reserv.reservation_id,
|
||||
# check_in_date_expected=miss_reserv.check_in,
|
||||
# ))
|
||||
|
||||
# # Массово сохраняем нарушения
|
||||
# if violations:
|
||||
# RoomDiscrepancy.objects.bulk_create(violations)
|
||||
# self.log_info(f"Записано {len(violations)} новых несоответствий.")
|
||||
|
||||
# # Обновляем флаги fraud_checked
|
||||
# UserActivityLog.objects.filter(id__in=[log.id for log in user_logs]).update(fraud_checked=True)
|
||||
# Reservation.objects.filter(id__in=[res.id for res in reservations]).update(fraud_checked=True)
|
||||
|
||||
# except Exception as e:
|
||||
# self.log_error(f"Ошибка при выполнении проверки: {e}")
|
||||
|
||||
# self.log_info("Проверка завершена.")
|
||||
|
||||
# # Функция для запуска из планировщика
|
||||
# def run_reservation_check():
|
||||
# """Запуск проверки через планировщик."""
|
||||
# logger.info("Планировщик вызывает run_reservation_check.")
|
||||
# try:
|
||||
# checker = ReservationChecker()
|
||||
# checker.run_check()
|
||||
# except Exception as e:
|
||||
# logger.error(f"Ошибка при запуске проверки: {e}")
|
||||
# logger.info("run_reservation_check завершена.")
|
||||
|
||||
|
||||
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from urllib.parse import parse_qs
|
||||
from django.utils import timezone
|
||||
from django.db.models import Q
|
||||
from django.db import connection
|
||||
from hotels.models import Reservation, Hotel
|
||||
from .models import UserActivityLog, ViolationLog, RoomDiscrepancy
|
||||
from .models import UserActivityLog, RoomDiscrepancy
|
||||
from touchh.utils.log import CustomLogger
|
||||
|
||||
# Настройка логирования
|
||||
logger = CustomLogger(__name__).get_logger()
|
||||
|
||||
|
||||
class ReservationChecker:
|
||||
"""
|
||||
Класс для проверки несоответствий между бронированиями и логами заселения.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Инициализация времени проверки и списка нарушений.
|
||||
"""
|
||||
self.checkin_diff_hours = 3
|
||||
self.checkin_diff_hours = 3 # Разрешенное отклонение от времени заселения
|
||||
|
||||
def log_info(self, message):
|
||||
logger.info(message)
|
||||
@@ -32,80 +155,105 @@ class ReservationChecker:
|
||||
logger.error(message)
|
||||
|
||||
def run_check(self):
|
||||
self.log_info(f"Запуск проверки.")
|
||||
"""Запуск проверки фродовых событий."""
|
||||
self.log_info("🔍 Запуск проверки фродовых данных.")
|
||||
try:
|
||||
hotels_map = {}
|
||||
hotels = Hotel.objects.all()
|
||||
for hotel in hotels:
|
||||
hotels_map[hotel.hotel_id] = hotel
|
||||
check_in_diff = timedelta(hours=self.checkin_diff_hours)
|
||||
|
||||
# Кэшируем отели в словарь для быстрого доступа
|
||||
hotels_map = {hotel.hotel_id: hotel for hotel in Hotel.objects.all()}
|
||||
|
||||
# Загружаем бронирования и активности пользователей
|
||||
user_logs = UserActivityLog.objects.filter(fraud_checked=False)
|
||||
reservations = Reservation.objects.filter(fraud_checked=False).select_related('hotel')
|
||||
missing = list(reservations)
|
||||
|
||||
# Преобразуем бронирования в словарь для быстрого поиска
|
||||
reservations_map = {
|
||||
(res.hotel.hotel_id, res.room_number): res for res in reservations
|
||||
}
|
||||
|
||||
violations = []
|
||||
check_in_diff = timedelta(hours=self.checkin_diff_hours)
|
||||
checked_reservations = set() # Сет для бронирований, которые были проверены
|
||||
|
||||
self.log_info(f"✅ Загружено {len(user_logs)} логов активности и {len(reservations)} бронирований.")
|
||||
|
||||
for user_log in user_logs:
|
||||
try:
|
||||
params = json.loads(user_log.url_parameters.replace("'", '"'))
|
||||
hotel_id = params['utm_content']
|
||||
room = params['utm_term']
|
||||
reserv = next((x for x in reservations
|
||||
if x.hotel.hotel_id == hotel_id and x.room_number == room
|
||||
and user_log.date_time >= x.check_in - check_in_diff and user_log.date_time < x.check_out
|
||||
), None)
|
||||
v_type = None
|
||||
params = json.loads(user_log.url_parameters.replace("'", '"')) if user_log.url_parameters else {}
|
||||
hotel_id = params.get('utm_content')
|
||||
room = params.get('utm_term')
|
||||
|
||||
if not hotel_id or not room:
|
||||
self.log_warning(f"🚫 Пропущен лог без hotel_id или room_number: {user_log.url_parameters}")
|
||||
continue # Пропускаем записи без нужных параметров
|
||||
|
||||
key = (hotel_id, room)
|
||||
reserv = reservations_map.get(key)
|
||||
|
||||
discrepancy_type = "match" # По умолчанию считаем, что всё соответствует
|
||||
|
||||
if reserv:
|
||||
if reserv in missing:
|
||||
missing.remove(reserv)
|
||||
checked_reservations.add(reserv)
|
||||
|
||||
if user_log.date_time < reserv.check_in:
|
||||
v_type = 'early'
|
||||
if user_log.date_time > reserv.check_in + check_in_diff:
|
||||
v_type = 'late'
|
||||
discrepancy_type = 'early'
|
||||
self.log_warning(f"⚠️ Обнаружено раннее заселение: {user_log.date_time} < {reserv.check_in}")
|
||||
elif user_log.date_time > reserv.check_in + check_in_diff:
|
||||
discrepancy_type = 'late'
|
||||
self.log_warning(f"⚠️ Обнаружено позднее заселение: {user_log.date_time} > {reserv.check_in + check_in_diff}")
|
||||
else:
|
||||
v_type = 'no_booking'
|
||||
discrepancy_type = 'no_booking'
|
||||
self.log_warning(f"🚨 Заселение без бронирования: {user_log.date_time} (Отель {hotel_id}, Комната {room})")
|
||||
|
||||
if v_type:
|
||||
violations.append(RoomDiscrepancy(
|
||||
hotel=hotels_map[hotel_id],
|
||||
room_number=room,
|
||||
discrepancy_type=v_type,
|
||||
booking_id=reserv.reservation_id if reserv else None,
|
||||
check_in_date_expected=reserv.check_in if reserv else None,
|
||||
check_in_date_actual=user_log.date_time,
|
||||
))
|
||||
violations.append(RoomDiscrepancy(
|
||||
hotel=hotels_map.get(hotel_id),
|
||||
room_number=room,
|
||||
discrepancy_type=discrepancy_type,
|
||||
booking_id=reserv.reservation_id if reserv else None,
|
||||
check_in_date_expected=reserv.check_in if reserv else None,
|
||||
check_in_date_actual=user_log.date_time,
|
||||
))
|
||||
|
||||
user_log.fraud_checked = True
|
||||
user_log.fraud_checked = True # Отмечаем логи как проверенные
|
||||
|
||||
except json.JSONDecodeError:
|
||||
self.log_error(f"❌ Ошибка декодирования JSON в URL-параметрах: {user_log.url_parameters}")
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.log_error(f"❌ Ошибка при обработке логов: {e}")
|
||||
|
||||
for miss_reserv in missing:
|
||||
violations.append(RoomDiscrepancy(
|
||||
hotel=miss_reserv.hotel,
|
||||
room_number=miss_reserv.room_number,
|
||||
discrepancy_type='missed',
|
||||
booking_id=miss_reserv.reservation_id,
|
||||
check_in_date_expected=miss_reserv.check_in,
|
||||
))
|
||||
# Добавляем пропущенные бронирования (неявки)
|
||||
for reserv in reservations:
|
||||
reserv.fraud_checked = True
|
||||
if reserv not in checked_reservations:
|
||||
violations.append(RoomDiscrepancy(
|
||||
hotel=reserv.hotel,
|
||||
room_number=reserv.room_number,
|
||||
discrepancy_type='missed',
|
||||
booking_id=reserv.reservation_id,
|
||||
check_in_date_expected=reserv.check_in,
|
||||
))
|
||||
self.log_warning(f"⚠️ Обнаружена неявка (missed) | Отель: {reserv.hotel.hotel_id}, Номер: {reserv.room_number}, Ожидаемая дата заезда: {reserv.check_in}")
|
||||
|
||||
RoomDiscrepancy.objects.bulk_create(violations)
|
||||
UserActivityLog.objects.bulk_update(user_logs, ['fraud_checked'], 1000)
|
||||
Reservation.objects.bulk_update(reservations, ['fraud_checked'], 1000)
|
||||
# Массово сохраняем все записи, включая корректные совпадения
|
||||
if violations:
|
||||
RoomDiscrepancy.objects.bulk_create(violations)
|
||||
self.log_info(f"✅ Записано {len(violations)} новых записей в RoomDiscrepancy.")
|
||||
|
||||
# Обновляем флаги fraud_checked
|
||||
UserActivityLog.objects.filter(id__in=[log.id for log in user_logs]).update(fraud_checked=True)
|
||||
Reservation.objects.filter(id__in=[res.id for res in reservations]).update(fraud_checked=True)
|
||||
|
||||
except Exception as e:
|
||||
self.log_error(f"Ошибка при выполнении проверки: {e}")
|
||||
self.log_info("Проверка завершена.")
|
||||
self.log_error(f"❌ Ошибка при выполнении проверки: {e}")
|
||||
|
||||
self.log_info("✅ Проверка фродовых данных завершена.")
|
||||
|
||||
# Функция для запуска из планировщика
|
||||
def run_reservation_check():
|
||||
logger.info("Планировщик вызывает run_reservation_check.")
|
||||
"""Запуск проверки через планировщик."""
|
||||
logger.info("📅 Планировщик вызывает run_reservation_check.")
|
||||
try:
|
||||
checker = ReservationChecker()
|
||||
checker.run_check()
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при запуске проверки: {e}")
|
||||
logger.info("run_reservation_check завершена.")
|
||||
logger.error(f"❌ Ошибка при запуске проверки: {e}")
|
||||
logger.info("✅ run_reservation_check завершена.")
|
||||
|
||||
@@ -85,14 +85,14 @@ class HotelAdmin(admin.ModelAdmin):
|
||||
class UserHotelAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'hotel')
|
||||
search_fields = ('user__username', 'hotel__name')
|
||||
# list_filter = ('hotel',)
|
||||
# ordering = ('-hotel',)
|
||||
list_filter = ('hotel',)
|
||||
ordering = ('-hotel',)
|
||||
|
||||
@admin.register(Reservation)
|
||||
class ReservationAdmin(admin.ModelAdmin):
|
||||
list_display = ('reservation_id', 'hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount')
|
||||
search_fields = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount')
|
||||
list_filter = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount')
|
||||
list_display = ('reservation_id', 'hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'fraud_checked')
|
||||
search_fields = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status')
|
||||
list_filter = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status')
|
||||
ordering = ('-check_in',)
|
||||
|
||||
@admin.register(Room)
|
||||
|
||||
@@ -111,8 +111,8 @@ class Reservation(models.Model):
|
||||
reservation_id = models.BigIntegerField(unique=True, verbose_name="ID бронирования")
|
||||
room_number = models.CharField(max_length=255, null=True, blank=True, verbose_name="Номер комнаты")
|
||||
room_type = models.CharField(max_length=255, verbose_name="Тип комнаты")
|
||||
check_in = models.DateTimeField(verbose_name="Дата заезда")
|
||||
check_out = models.DateTimeField(verbose_name="Дата выезда")
|
||||
check_in = models.DateTimeField(verbose_name="Дата заезда", null=True, blank=True)
|
||||
check_out = models.DateTimeField(verbose_name="Дата выезда", null=True, blank=True)
|
||||
status = models.CharField(max_length=50, verbose_name="Статус")
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Цена")
|
||||
discount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name="Скидка")
|
||||
|
||||
@@ -1,9 +1,146 @@
|
||||
# ecvi_pms.py
|
||||
# # 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 requests
|
||||
import json
|
||||
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
|
||||
@@ -20,7 +157,7 @@ class EcviPMSPlugin(BasePMSPlugin):
|
||||
if not self.hotel.pms:
|
||||
raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.")
|
||||
|
||||
self.api_url = self.hotel.pms.url.rstrip("/")
|
||||
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
|
||||
@@ -35,7 +172,14 @@ class EcviPMSPlugin(BasePMSPlugin):
|
||||
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",
|
||||
@@ -48,6 +192,9 @@ class EcviPMSPlugin(BasePMSPlugin):
|
||||
}
|
||||
|
||||
async def _fetch_data(self):
|
||||
"""
|
||||
Получает данные из PMS API и сохраняет их в базу.
|
||||
"""
|
||||
headers = {"Content-Type": "application/json"}
|
||||
data = {"token": self.token}
|
||||
|
||||
@@ -59,22 +206,16 @@ class EcviPMSPlugin(BasePMSPlugin):
|
||||
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)
|
||||
# Сохраняем весь ответ 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)
|
||||
|
||||
# Сохранение данных во временный 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)
|
||||
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 {
|
||||
@@ -84,33 +225,81 @@ class EcviPMSPlugin(BasePMSPlugin):
|
||||
}
|
||||
|
||||
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['checkin']
|
||||
checkout = item['checkout']
|
||||
if checkin in [None, "0000-00-00 00:00:00"] or checkout in [None, "0000-00-00 00:00:00"]:
|
||||
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': checkin,
|
||||
'check_out': checkout,
|
||||
'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}")
|
||||
self.logger.error(f"Ошибка сохранения бронирования: {e}")
|
||||
errors.append(str(e))
|
||||
|
||||
return {
|
||||
@@ -121,6 +310,9 @@ class EcviPMSPlugin(BasePMSPlugin):
|
||||
|
||||
@staticmethod
|
||||
def _parse_date(date_str, formats):
|
||||
"""
|
||||
Парсит дату, пытаясь использовать несколько форматов.
|
||||
"""
|
||||
for fmt in formats:
|
||||
try:
|
||||
return datetime.strptime(date_str, fmt)
|
||||
@@ -129,6 +321,9 @@ class EcviPMSPlugin(BasePMSPlugin):
|
||||
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):
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user