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
|
import json
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from urllib.parse import parse_qs
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db import connection
|
|
||||||
from hotels.models import Reservation, Hotel
|
from hotels.models import Reservation, Hotel
|
||||||
from .models import UserActivityLog, ViolationLog, RoomDiscrepancy
|
from .models import UserActivityLog, RoomDiscrepancy
|
||||||
from touchh.utils.log import CustomLogger
|
from touchh.utils.log import CustomLogger
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logger = CustomLogger(__name__).get_logger()
|
logger = CustomLogger(__name__).get_logger()
|
||||||
|
|
||||||
|
|
||||||
class ReservationChecker:
|
class ReservationChecker:
|
||||||
"""
|
"""
|
||||||
Класс для проверки несоответствий между бронированиями и логами заселения.
|
Класс для проверки несоответствий между бронированиями и логами заселения.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
self.checkin_diff_hours = 3 # Разрешенное отклонение от времени заселения
|
||||||
Инициализация времени проверки и списка нарушений.
|
|
||||||
"""
|
|
||||||
self.checkin_diff_hours = 3
|
|
||||||
|
|
||||||
def log_info(self, message):
|
def log_info(self, message):
|
||||||
logger.info(message)
|
logger.info(message)
|
||||||
@@ -32,80 +155,105 @@ class ReservationChecker:
|
|||||||
logger.error(message)
|
logger.error(message)
|
||||||
|
|
||||||
def run_check(self):
|
def run_check(self):
|
||||||
self.log_info(f"Запуск проверки.")
|
"""Запуск проверки фродовых событий."""
|
||||||
|
self.log_info("🔍 Запуск проверки фродовых данных.")
|
||||||
try:
|
try:
|
||||||
hotels_map = {}
|
check_in_diff = timedelta(hours=self.checkin_diff_hours)
|
||||||
hotels = Hotel.objects.all()
|
|
||||||
for hotel in hotels:
|
|
||||||
hotels_map[hotel.hotel_id] = hotel
|
|
||||||
|
|
||||||
|
# Кэшируем отели в словарь для быстрого доступа
|
||||||
|
hotels_map = {hotel.hotel_id: hotel for hotel in Hotel.objects.all()}
|
||||||
|
|
||||||
|
# Загружаем бронирования и активности пользователей
|
||||||
user_logs = UserActivityLog.objects.filter(fraud_checked=False)
|
user_logs = UserActivityLog.objects.filter(fraud_checked=False)
|
||||||
reservations = Reservation.objects.filter(fraud_checked=False).select_related('hotel')
|
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 = []
|
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:
|
for user_log in user_logs:
|
||||||
try:
|
try:
|
||||||
params = json.loads(user_log.url_parameters.replace("'", '"'))
|
params = json.loads(user_log.url_parameters.replace("'", '"')) if user_log.url_parameters else {}
|
||||||
hotel_id = params['utm_content']
|
hotel_id = params.get('utm_content')
|
||||||
room = params['utm_term']
|
room = params.get('utm_term')
|
||||||
reserv = next((x for x in reservations
|
|
||||||
if x.hotel.hotel_id == hotel_id and x.room_number == room
|
if not hotel_id or not room:
|
||||||
and user_log.date_time >= x.check_in - check_in_diff and user_log.date_time < x.check_out
|
self.log_warning(f"🚫 Пропущен лог без hotel_id или room_number: {user_log.url_parameters}")
|
||||||
), None)
|
continue # Пропускаем записи без нужных параметров
|
||||||
v_type = None
|
|
||||||
if reserv:
|
key = (hotel_id, room)
|
||||||
if reserv in missing:
|
reserv = reservations_map.get(key)
|
||||||
missing.remove(reserv)
|
|
||||||
if user_log.date_time < reserv.check_in:
|
discrepancy_type = "match" # По умолчанию считаем, что всё соответствует
|
||||||
v_type = 'early'
|
|
||||||
if user_log.date_time > reserv.check_in + check_in_diff:
|
if reserv:
|
||||||
v_type = 'late'
|
checked_reservations.add(reserv)
|
||||||
else:
|
|
||||||
v_type = 'no_booking'
|
if user_log.date_time < reserv.check_in:
|
||||||
|
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:
|
||||||
|
discrepancy_type = 'no_booking'
|
||||||
|
self.log_warning(f"🚨 Заселение без бронирования: {user_log.date_time} (Отель {hotel_id}, Комната {room})")
|
||||||
|
|
||||||
if v_type:
|
|
||||||
violations.append(RoomDiscrepancy(
|
violations.append(RoomDiscrepancy(
|
||||||
hotel=hotels_map[hotel_id],
|
hotel=hotels_map.get(hotel_id),
|
||||||
room_number=room,
|
room_number=room,
|
||||||
discrepancy_type=v_type,
|
discrepancy_type=discrepancy_type,
|
||||||
booking_id=reserv.reservation_id if reserv else None,
|
booking_id=reserv.reservation_id if reserv else None,
|
||||||
check_in_date_expected=reserv.check_in if reserv else None,
|
check_in_date_expected=reserv.check_in if reserv else None,
|
||||||
check_in_date_actual=user_log.date_time,
|
check_in_date_actual=user_log.date_time,
|
||||||
))
|
))
|
||||||
|
|
||||||
user_log.fraud_checked = True
|
user_log.fraud_checked = True # Отмечаем логи как проверенные
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
|
|
||||||
for miss_reserv in missing:
|
except json.JSONDecodeError:
|
||||||
violations.append(RoomDiscrepancy(
|
self.log_error(f"❌ Ошибка декодирования JSON в URL-параметрах: {user_log.url_parameters}")
|
||||||
hotel=miss_reserv.hotel,
|
except Exception as e:
|
||||||
room_number=miss_reserv.room_number,
|
self.log_error(f"❌ Ошибка при обработке логов: {e}")
|
||||||
discrepancy_type='missed',
|
|
||||||
booking_id=miss_reserv.reservation_id,
|
# Добавляем пропущенные бронирования (неявки)
|
||||||
check_in_date_expected=miss_reserv.check_in,
|
|
||||||
))
|
|
||||||
for reserv in reservations:
|
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}")
|
||||||
|
|
||||||
|
# Массово сохраняем все записи, включая корректные совпадения
|
||||||
|
if violations:
|
||||||
RoomDiscrepancy.objects.bulk_create(violations)
|
RoomDiscrepancy.objects.bulk_create(violations)
|
||||||
UserActivityLog.objects.bulk_update(user_logs, ['fraud_checked'], 1000)
|
self.log_info(f"✅ Записано {len(violations)} новых записей в RoomDiscrepancy.")
|
||||||
Reservation.objects.bulk_update(reservations, ['fraud_checked'], 1000)
|
|
||||||
|
|
||||||
|
# Обновляем флаги 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:
|
except Exception as e:
|
||||||
self.log_error(f"Ошибка при выполнении проверки: {e}")
|
self.log_error(f"❌ Ошибка при выполнении проверки: {e}")
|
||||||
self.log_info("Проверка завершена.")
|
|
||||||
|
self.log_info("✅ Проверка фродовых данных завершена.")
|
||||||
|
|
||||||
# Функция для запуска из планировщика
|
# Функция для запуска из планировщика
|
||||||
def run_reservation_check():
|
def run_reservation_check():
|
||||||
logger.info("Планировщик вызывает run_reservation_check.")
|
"""Запуск проверки через планировщик."""
|
||||||
|
logger.info("📅 Планировщик вызывает run_reservation_check.")
|
||||||
try:
|
try:
|
||||||
checker = ReservationChecker()
|
checker = ReservationChecker()
|
||||||
checker.run_check()
|
checker.run_check()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при запуске проверки: {e}")
|
logger.error(f"❌ Ошибка при запуске проверки: {e}")
|
||||||
logger.info("run_reservation_check завершена.")
|
logger.info("✅ run_reservation_check завершена.")
|
||||||
|
|||||||
@@ -85,14 +85,14 @@ class HotelAdmin(admin.ModelAdmin):
|
|||||||
class UserHotelAdmin(admin.ModelAdmin):
|
class UserHotelAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'hotel')
|
list_display = ('user', 'hotel')
|
||||||
search_fields = ('user__username', 'hotel__name')
|
search_fields = ('user__username', 'hotel__name')
|
||||||
# list_filter = ('hotel',)
|
list_filter = ('hotel',)
|
||||||
# ordering = ('-hotel',)
|
ordering = ('-hotel',)
|
||||||
|
|
||||||
@admin.register(Reservation)
|
@admin.register(Reservation)
|
||||||
class ReservationAdmin(admin.ModelAdmin):
|
class ReservationAdmin(admin.ModelAdmin):
|
||||||
list_display = ('reservation_id', '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', 'price', 'discount')
|
search_fields = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status')
|
||||||
list_filter = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount')
|
list_filter = ('hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status')
|
||||||
ordering = ('-check_in',)
|
ordering = ('-check_in',)
|
||||||
|
|
||||||
@admin.register(Room)
|
@admin.register(Room)
|
||||||
|
|||||||
@@ -111,8 +111,8 @@ class Reservation(models.Model):
|
|||||||
reservation_id = models.BigIntegerField(unique=True, verbose_name="ID бронирования")
|
reservation_id = models.BigIntegerField(unique=True, verbose_name="ID бронирования")
|
||||||
room_number = models.CharField(max_length=255, null=True, blank=True, verbose_name="Номер комнаты")
|
room_number = models.CharField(max_length=255, null=True, blank=True, verbose_name="Номер комнаты")
|
||||||
room_type = models.CharField(max_length=255, verbose_name="Тип комнаты")
|
room_type = models.CharField(max_length=255, verbose_name="Тип комнаты")
|
||||||
check_in = models.DateTimeField(verbose_name="Дата заезда")
|
check_in = models.DateTimeField(verbose_name="Дата заезда", null=True, blank=True)
|
||||||
check_out = models.DateTimeField(verbose_name="Дата выезда")
|
check_out = models.DateTimeField(verbose_name="Дата выезда", null=True, blank=True)
|
||||||
status = models.CharField(max_length=50, verbose_name="Статус")
|
status = models.CharField(max_length=50, verbose_name="Статус")
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, 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="Скидка")
|
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 logging
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
import requests
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from hotels.models import Hotel, Reservation
|
from hotels.models import Hotel, Reservation
|
||||||
from .base_plugin import BasePMSPlugin
|
from .base_plugin import BasePMSPlugin
|
||||||
@@ -20,7 +157,7 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
if not self.hotel.pms:
|
if not self.hotel.pms:
|
||||||
raise ValueError(f"Отель {self.hotel.name} не имеет связанной 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.token = self.hotel.pms.token
|
||||||
self.username = self.hotel.pms.username
|
self.username = self.hotel.pms.username
|
||||||
self.password = self.hotel.pms.password
|
self.password = self.hotel.pms.password
|
||||||
@@ -35,7 +172,14 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
self.logger.addHandler(handler_file)
|
self.logger.addHandler(handler_file)
|
||||||
self.logger.setLevel(logging.WARNING)
|
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):
|
def get_default_parser_settings(self):
|
||||||
|
"""
|
||||||
|
Возвращает настройки парсера по умолчанию.
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
"field_mapping": {
|
"field_mapping": {
|
||||||
"check_in": "checkin",
|
"check_in": "checkin",
|
||||||
@@ -48,6 +192,9 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
async def _fetch_data(self):
|
async def _fetch_data(self):
|
||||||
|
"""
|
||||||
|
Получает данные из PMS API и сохраняет их в базу.
|
||||||
|
"""
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
data = {"token": self.token}
|
data = {"token": self.token}
|
||||||
|
|
||||||
@@ -59,22 +206,16 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
self.logger.debug(f"Полученные данные с API: {response_data}")
|
self.logger.debug(f"Полученные данные с API: {response_data}")
|
||||||
|
|
||||||
# Группировка данных по номеру комнаты
|
# Сохраняем весь ответ API в файл для анализа
|
||||||
structured_data = {}
|
file_name = f"ecvi_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||||
for item in response_data:
|
file_path = os.path.join(self.data_dir, file_name)
|
||||||
room_number = item.get("room_name", "unknown")
|
with open(file_path, "w", encoding="utf-8") as f:
|
||||||
if room_number not in structured_data:
|
json.dump(response_data, f, ensure_ascii=False, indent=4)
|
||||||
structured_data[room_number] = []
|
|
||||||
structured_data[room_number].append(item)
|
|
||||||
|
|
||||||
# Сохранение данных во временный JSON-файл
|
self.logger.info(f"API-ответ сохранен в файл: {file_path}")
|
||||||
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)
|
return await self._process_data(response_data)
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
self.logger.error(f"Ошибка API: {e}")
|
self.logger.error(f"Ошибка API: {e}")
|
||||||
return {
|
return {
|
||||||
@@ -84,33 +225,81 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
async def _process_data(self, data):
|
async def _process_data(self, data):
|
||||||
|
"""
|
||||||
|
Обрабатывает данные и сохраняет их в базу.
|
||||||
|
"""
|
||||||
processed_items = 0
|
processed_items = 0
|
||||||
errors = []
|
errors = []
|
||||||
date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"]
|
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:
|
for item in data:
|
||||||
try:
|
try:
|
||||||
checkin = item['checkin']
|
checkin = item.get('checkin')
|
||||||
checkout = item['checkout']
|
checkout = item.get('checkout')
|
||||||
if checkin in [None, "0000-00-00 00:00:00"] or checkout in [None, "0000-00-00 00:00:00"]:
|
|
||||||
|
# Фильтруем записи с некорректными датами
|
||||||
|
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
|
continue
|
||||||
|
|
||||||
checkin = self._parse_date(checkin, date_formats)
|
checkin = self._parse_date(checkin, date_formats)
|
||||||
checkout = self._parse_date(checkout, 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, created = await sync_to_async(Reservation.objects.update_or_create)(
|
||||||
reservation_id=item['task_id'],
|
reservation_id=item['task_id'],
|
||||||
defaults={
|
defaults={
|
||||||
'room_number': item['room_name'],
|
'room_number': item['room_name'],
|
||||||
'room_type': item['room_type'],
|
'room_type': item['room_type'],
|
||||||
'check_in': checkin,
|
'check_in': self._parse_date(item['checkin'], date_formats),
|
||||||
'check_out': checkout,
|
'check_out': self._parse_date(item['checkout'], date_formats),
|
||||||
'status': item['occupancy'],
|
'status': item['occupancy'],
|
||||||
'hotel': self.hotel,
|
'hotel': self.hotel,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
self.logger.debug(f"Создана новая резервация: {reservation.reservation_id}")
|
||||||
|
else:
|
||||||
|
self.logger.debug(f"Обновлена существующая резервация: {reservation.reservation_id}")
|
||||||
|
|
||||||
processed_items += 1
|
processed_items += 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка обработки записи: {e}")
|
self.logger.error(f"Ошибка сохранения бронирования: {e}")
|
||||||
errors.append(str(e))
|
errors.append(str(e))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -121,6 +310,9 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_date(date_str, formats):
|
def _parse_date(date_str, formats):
|
||||||
|
"""
|
||||||
|
Парсит дату, пытаясь использовать несколько форматов.
|
||||||
|
"""
|
||||||
for fmt in formats:
|
for fmt in formats:
|
||||||
try:
|
try:
|
||||||
return datetime.strptime(date_str, fmt)
|
return datetime.strptime(date_str, fmt)
|
||||||
@@ -129,6 +321,9 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
raise ValueError(f"Дата '{date_str}' не соответствует ожидаемым форматам: {formats}")
|
raise ValueError(f"Дата '{date_str}' не соответствует ожидаемым форматам: {formats}")
|
||||||
|
|
||||||
def validate_plugin(self):
|
def validate_plugin(self):
|
||||||
|
"""
|
||||||
|
Проверка корректности реализации плагина.
|
||||||
|
"""
|
||||||
required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"]
|
required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"]
|
||||||
for method in required_methods:
|
for method in required_methods:
|
||||||
if not hasattr(self, method):
|
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