data_sync
This commit is contained in:
@@ -4,7 +4,7 @@ from django.http import JsonResponse
|
|||||||
from django.shortcuts import redirect, get_object_or_404
|
from django.shortcuts import redirect, get_object_or_404
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from antifroud.models import UserActivityLog, ExternalDBSettings, RoomDiscrepancy, ImportedHotel, SyncLog
|
from antifroud.models import UserActivityLog, ExternalDBSettings, RoomDiscrepancy, ImportedHotel, SyncLog, ViolationLog
|
||||||
from hotels.models import Hotel
|
from hotels.models import Hotel
|
||||||
import pymysql
|
import pymysql
|
||||||
import logging
|
import logging
|
||||||
@@ -112,7 +112,7 @@ class ExternalDBSettingsAdmin(admin.ModelAdmin):
|
|||||||
class UserActivityLogAdmin(admin.ModelAdmin):
|
class UserActivityLogAdmin(admin.ModelAdmin):
|
||||||
list_display = ("id", "timestamp", "date_time", "page_id", "url_parameters", "created", "page_title", "type", "hits")
|
list_display = ("id", "timestamp", "date_time", "page_id", "url_parameters", "created", "page_title", "type", "hits")
|
||||||
search_fields = ("page_title", "url_parameters")
|
search_fields = ("page_title", "url_parameters")
|
||||||
list_filter = ("type", "created")
|
list_filter = ("page_title", "created")
|
||||||
readonly_fields = ("created", "timestamp")
|
readonly_fields = ("created", "timestamp")
|
||||||
|
|
||||||
|
|
||||||
@@ -184,4 +184,15 @@ class SyncLogAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SyncLog
|
model = SyncLog
|
||||||
fields = ['hotel', 'received_records', 'processed_records']
|
fields = ['hotel', 'received_records', 'processed_records']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ViolationLog)
|
||||||
|
class ViolationLogAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['id', 'hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at']
|
||||||
|
search_fields = ['id', 'hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at']
|
||||||
|
list_filter = ['id', 'hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at']
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ViolationLog
|
||||||
|
fields = ['hotel', 'room_number', 'created_at', 'violation_type', 'violation_details', 'detected_at']
|
||||||
79
antifroud/check_fraud.py
Normal file
79
antifroud/check_fraud.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
from django.db.models import Q
|
||||||
|
from hotels.models import Reservation, Hotel
|
||||||
|
from .models import UserActivityLog, ViolationLog
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(level=logging.INFO) # Устанавливаем уровень логирования
|
||||||
|
logger = logging.getLogger(__name__) # Создаем логгер для текущего модуля
|
||||||
|
|
||||||
|
def check_reservations_and_generate_report():
|
||||||
|
now = datetime.now()
|
||||||
|
start_time = (now - timedelta(hours=12))
|
||||||
|
end_time = now
|
||||||
|
logger.info(f"Starting reservation check from {start_time} to {end_time}")
|
||||||
|
|
||||||
|
# Получаем логи активности за период
|
||||||
|
user_logs = UserActivityLog.objects.filter(created__range=(start_time, end_time))
|
||||||
|
logger.info(f"Found {len(user_logs)} logs for analysis.")
|
||||||
|
|
||||||
|
violations = [] # Список для записи нарушений
|
||||||
|
|
||||||
|
for i, log in enumerate(user_logs, start=1):
|
||||||
|
logger.debug(f"Processing log {i}: {log}")
|
||||||
|
|
||||||
|
if not log.url_parameters:
|
||||||
|
logger.warning(f"Log {i} skipped due to missing URL parameters.")
|
||||||
|
continue # Пропускаем записи без параметров URL
|
||||||
|
|
||||||
|
# Парсим параметры URL
|
||||||
|
params = parse_qs(log.url_parameters)
|
||||||
|
external_id = params.get("utm_content", [None])[0] # ID отеля
|
||||||
|
room_number = params.get("utm_term", [None])[0] # Номер комнаты
|
||||||
|
|
||||||
|
logger.debug(f"Log {i} parsed parameters: external_id={external_id}, room_number={room_number}")
|
||||||
|
|
||||||
|
if not external_id or not room_number:
|
||||||
|
logger.warning(f"Log {i} skipped due to missing external_id or room_number.")
|
||||||
|
continue # Пропускаем, если данные не извлечены
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Находим отель по external_id
|
||||||
|
hotel = Hotel.objects.get(external_id=external_id)
|
||||||
|
logger.debug(f"Log {i}: Found hotel {hotel.name} with external_id {external_id}.")
|
||||||
|
except Hotel.DoesNotExist:
|
||||||
|
logger.error(f"Log {i} skipped: No hotel found with external_id {external_id}.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ищем бронирование в Reservation
|
||||||
|
matching_reservations = Reservation.objects.filter(
|
||||||
|
hotel=hotel,
|
||||||
|
room_number=room_number,
|
||||||
|
check_in__lte=log.created,
|
||||||
|
check_out__gte=log.created
|
||||||
|
)
|
||||||
|
logger.debug(f"Log {i}: Found {len(matching_reservations)} matching reservations.")
|
||||||
|
|
||||||
|
if not matching_reservations.exists():
|
||||||
|
# Если бронирование не найдено — записываем нарушение
|
||||||
|
violation_details = (
|
||||||
|
f"Log {i}: No reservation found for room {room_number} in hotel {external_id} at {log.created}."
|
||||||
|
)
|
||||||
|
violations.append(ViolationLog(
|
||||||
|
hotel=hotel,
|
||||||
|
room_number=room_number,
|
||||||
|
violation_type="missed",
|
||||||
|
violation_details=violation_details
|
||||||
|
))
|
||||||
|
logger.warning(f"Log {i}: Violation recorded - {violation_details}")
|
||||||
|
|
||||||
|
# Сохраняем все нарушения в базу
|
||||||
|
if violations:
|
||||||
|
ViolationLog.objects.bulk_create(violations)
|
||||||
|
logger.info(f"Created {len(violations)} records in violation log.")
|
||||||
|
else:
|
||||||
|
logger.info("No violations found during this check.")
|
||||||
|
|
||||||
|
logger.info("Reservation check completed.")
|
||||||
@@ -223,7 +223,7 @@ class DataSyncManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Генерация external_id в формате 'hotel_name_hotel_id'
|
# Генерация external_id в формате 'hotel_name_hotel_id'
|
||||||
external_id = f"{hotel_name}_{hotel_id}"
|
external_id = f"{hotel_name}"
|
||||||
|
|
||||||
# Проверяем, существует ли запись с таким external_id в ImportedHotel
|
# Проверяем, существует ли запись с таким external_id в ImportedHotel
|
||||||
existing_hotel = ImportedHotel.objects.filter(external_id=external_id).first()
|
existing_hotel = ImportedHotel.objects.filter(external_id=external_id).first()
|
||||||
|
|||||||
31
antifroud/migrations/0012_violationlog.py
Normal file
31
antifroud/migrations/0012_violationlog.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2024-12-16 23:37
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('antifroud', '0011_alter_importedhotel_external_id'),
|
||||||
|
('hotels', '0010_alter_hotel_timezone'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ViolationLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('room_number', models.CharField(blank=True, max_length=50, null=True, verbose_name='Номер комнаты')),
|
||||||
|
('violation_type', models.CharField(choices=[('missed', 'Неявка'), ('early', 'Раннее заселение'), ('late', 'Позднее заселение')], max_length=50, verbose_name='Тип нарушения')),
|
||||||
|
('violation_details', models.TextField(blank=True, null=True, verbose_name='Детали нарушения')),
|
||||||
|
('detected_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата обнаружения')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||||
|
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Журнал нарушений',
|
||||||
|
'verbose_name_plural': 'Журналы нарушений',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -166,4 +166,24 @@ class SyncLog(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Журнал синхронизации"
|
verbose_name = "Журнал синхронизации"
|
||||||
verbose_name_plural = "Журналы синхронизации"
|
verbose_name_plural = "Журналы синхронизации"
|
||||||
|
|
||||||
|
|
||||||
|
class ViolationLog(models.Model):
|
||||||
|
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, verbose_name="Отель")
|
||||||
|
room_number = models.CharField(max_length=50, verbose_name="Номер комнаты", null=True, blank=True)
|
||||||
|
violation_type = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=[("missed", "Неявка"), ("early", "Раннее заселение"), ("late", "Позднее заселение")],
|
||||||
|
verbose_name="Тип нарушения"
|
||||||
|
)
|
||||||
|
violation_details = models.TextField(verbose_name="Детали нарушения", blank=True, null=True)
|
||||||
|
detected_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата обнаружения")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.hotel.name} - {self.room_number or 'N/A'}: {self.violation_type}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Журнал нарушений"
|
||||||
|
verbose_name_plural = "Журналы нарушений"
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ class APIConfiguration(models.Model):
|
|||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
class Hotel(models.Model):
|
class Hotel(models.Model):
|
||||||
id = models.BigAutoField(primary_key=True, auto_created=True, verbose_name="ID")
|
|
||||||
name = models.CharField(max_length=255, verbose_name="Название отеля")
|
name = models.CharField(max_length=255, verbose_name="Название отеля")
|
||||||
hotel_id = models.CharField(max_length=255, unique=True, null=True, blank=True, verbose_name="ID отеля")
|
hotel_id = models.CharField(max_length=255, unique=True, null=True, blank=True, verbose_name="ID отеля")
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
|
||||||
@@ -49,7 +48,25 @@ class Hotel(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Отель"
|
verbose_name = "Отель"
|
||||||
verbose_name_plural = "Отели"
|
verbose_name_plural = "Отели"
|
||||||
|
|
||||||
|
class Room(models.Model):
|
||||||
|
"""
|
||||||
|
Модель номера отеля.
|
||||||
|
"""
|
||||||
|
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name="rooms", verbose_name="Отель")
|
||||||
|
number = models.CharField(max_length=50, verbose_name="Номер комнаты")
|
||||||
|
external_id = models.CharField(max_length=255, unique=True, verbose_name="Внешний ID комнаты")
|
||||||
|
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||||
|
updated_at = models.DateTimeField(auto_now=True, verbose_name="Дата обновления")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.hotel.name} - {self.number}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Номер"
|
||||||
|
verbose_name_plural = "Номера"
|
||||||
|
unique_together = ("hotel", "number") # Уникальность пары (отель, номер)
|
||||||
|
|
||||||
class UserHotel(models.Model):
|
class UserHotel(models.Model):
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class EcviPMSPlugin(BasePMSPlugin):
|
|||||||
filtered_data = [
|
filtered_data = [
|
||||||
{
|
{
|
||||||
'reservation_id': item.get('task_id'),
|
'reservation_id': item.get('task_id'),
|
||||||
'room_number': item.get('room_number'),
|
'room_number': item.get('room_name'),
|
||||||
'room_type': item.get('room_type'),
|
'room_type': item.get('room_type'),
|
||||||
'checkin': datetime.strptime(item.get('checkin'), '%Y-%m-%d %H:%M:%S'),
|
'checkin': datetime.strptime(item.get('checkin'), '%Y-%m-%d %H:%M:%S'),
|
||||||
'checkout': datetime.strptime(item.get('checkout'), '%Y-%m-%d %H:%M:%S'),
|
'checkout': datetime.strptime(item.get('checkout'), '%Y-%m-%d %H:%M:%S'),
|
||||||
|
|||||||
@@ -65,4 +65,5 @@ urllib3==2.2.3
|
|||||||
user-agents==2.2.0
|
user-agents==2.2.0
|
||||||
yarl==1.18.3
|
yarl==1.18.3
|
||||||
mysqlclient
|
mysqlclient
|
||||||
chardet
|
chardet
|
||||||
|
decouple
|
||||||
Reference in New Issue
Block a user