From 66750015e238830ab7bcd533ae9012f3a24a60ff Mon Sep 17 00:00:00 2001 From: trevor Date: Tue, 17 Dec 2024 11:49:26 +0900 Subject: [PATCH] data_sync --- antifroud/admin.py | 17 ++++- antifroud/check_fraud.py | 79 +++++++++++++++++++++++ antifroud/data_sync.py | 2 +- antifroud/migrations/0012_violationlog.py | 31 +++++++++ antifroud/models.py | 22 ++++++- hotels/models.py | 19 +++++- pms_integration/plugins/ecvi_pms.py | 2 +- requierments.txt | 3 +- 8 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 antifroud/check_fraud.py create mode 100644 antifroud/migrations/0012_violationlog.py diff --git a/antifroud/admin.py b/antifroud/admin.py index 8d1ef1f7..93aa8752 100644 --- a/antifroud/admin.py +++ b/antifroud/admin.py @@ -4,7 +4,7 @@ from django.http import JsonResponse from django.shortcuts import redirect, get_object_or_404 from django.contrib import messages 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 import pymysql import logging @@ -112,7 +112,7 @@ class ExternalDBSettingsAdmin(admin.ModelAdmin): class UserActivityLogAdmin(admin.ModelAdmin): list_display = ("id", "timestamp", "date_time", "page_id", "url_parameters", "created", "page_title", "type", "hits") search_fields = ("page_title", "url_parameters") - list_filter = ("type", "created") + list_filter = ("page_title", "created") readonly_fields = ("created", "timestamp") @@ -184,4 +184,15 @@ class SyncLogAdmin(admin.ModelAdmin): class Meta: model = SyncLog - fields = ['hotel', 'received_records', 'processed_records'] \ No newline at end of file + 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'] \ No newline at end of file diff --git a/antifroud/check_fraud.py b/antifroud/check_fraud.py new file mode 100644 index 00000000..9cf676bf --- /dev/null +++ b/antifroud/check_fraud.py @@ -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.") diff --git a/antifroud/data_sync.py b/antifroud/data_sync.py index 75806207..81ac3132 100644 --- a/antifroud/data_sync.py +++ b/antifroud/data_sync.py @@ -223,7 +223,7 @@ class DataSyncManager: return None # Генерация external_id в формате 'hotel_name_hotel_id' - external_id = f"{hotel_name}_{hotel_id}" + external_id = f"{hotel_name}" # Проверяем, существует ли запись с таким external_id в ImportedHotel existing_hotel = ImportedHotel.objects.filter(external_id=external_id).first() diff --git a/antifroud/migrations/0012_violationlog.py b/antifroud/migrations/0012_violationlog.py new file mode 100644 index 00000000..6c681a31 --- /dev/null +++ b/antifroud/migrations/0012_violationlog.py @@ -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': 'Журналы нарушений', + }, + ), + ] diff --git a/antifroud/models.py b/antifroud/models.py index 0503a7f8..f47e1d84 100644 --- a/antifroud/models.py +++ b/antifroud/models.py @@ -166,4 +166,24 @@ class SyncLog(models.Model): class Meta: verbose_name = "Журнал синхронизации" - verbose_name_plural = "Журналы синхронизации" \ No newline at end of file + 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 = "Журналы нарушений" diff --git a/hotels/models.py b/hotels/models.py index 93b5a863..de59702d 100644 --- a/hotels/models.py +++ b/hotels/models.py @@ -20,7 +20,6 @@ class APIConfiguration(models.Model): import pytz class Hotel(models.Model): - id = models.BigAutoField(primary_key=True, auto_created=True, verbose_name="ID") name = models.CharField(max_length=255, verbose_name="Название отеля") 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="Создан") @@ -49,7 +48,25 @@ class Hotel(models.Model): class Meta: verbose_name = "Отель" 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): user = models.ForeignKey( diff --git a/pms_integration/plugins/ecvi_pms.py b/pms_integration/plugins/ecvi_pms.py index dea3be03..64fa5246 100644 --- a/pms_integration/plugins/ecvi_pms.py +++ b/pms_integration/plugins/ecvi_pms.py @@ -79,7 +79,7 @@ class EcviPMSPlugin(BasePMSPlugin): filtered_data = [ { 'reservation_id': item.get('task_id'), - 'room_number': item.get('room_number'), + 'room_number': item.get('room_name'), 'room_type': item.get('room_type'), 'checkin': datetime.strptime(item.get('checkin'), '%Y-%m-%d %H:%M:%S'), 'checkout': datetime.strptime(item.get('checkout'), '%Y-%m-%d %H:%M:%S'), diff --git a/requierments.txt b/requierments.txt index 9c6393a2..6c77e403 100644 --- a/requierments.txt +++ b/requierments.txt @@ -65,4 +65,5 @@ urllib3==2.2.3 user-agents==2.2.0 yarl==1.18.3 mysqlclient -chardet \ No newline at end of file +chardet +decouple \ No newline at end of file