Merge branch 'zorn-dev' into PMSManager_refactor
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
|
import json
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from urllib.parse import parse_qs
|
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
|
from .models import UserActivityLog, ViolationLog, RoomDiscrepancy
|
||||||
from touchh.utils.log import CustomLogger
|
from touchh.utils.log import CustomLogger
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logger = CustomLogger(__name__).get_logger()
|
logger = CustomLogger(__name__).get_logger()
|
||||||
@@ -18,9 +20,7 @@ class ReservationChecker:
|
|||||||
"""
|
"""
|
||||||
Инициализация времени проверки и списка нарушений.
|
Инициализация времени проверки и списка нарушений.
|
||||||
"""
|
"""
|
||||||
self.start_time = timezone.now() - timedelta(days=30)
|
self.checkin_diff_hours = 3
|
||||||
self.end_time = timezone.now()
|
|
||||||
self.violations = []
|
|
||||||
|
|
||||||
def log_info(self, message):
|
def log_info(self, message):
|
||||||
logger.info(message)
|
logger.info(message)
|
||||||
@@ -31,102 +31,71 @@ class ReservationChecker:
|
|||||||
def log_error(self, message):
|
def log_error(self, message):
|
||||||
logger.error(message)
|
logger.error(message)
|
||||||
|
|
||||||
def fetch_user_logs(self):
|
|
||||||
try:
|
|
||||||
self.log_info("Начинается извлечение логов активности пользователей.")
|
|
||||||
user_logs = UserActivityLog.objects.filter(created__range=(self.start_time, self.end_time))
|
|
||||||
self.log_info(f"Найдено {user_logs.count()} логов активности для анализа.")
|
|
||||||
return user_logs
|
|
||||||
except Exception as e:
|
|
||||||
self.log_error(f"Ошибка при извлечении логов активности: {e}")
|
|
||||||
return UserActivityLog.objects.none()
|
|
||||||
|
|
||||||
def fetch_reservations(self):
|
|
||||||
try:
|
|
||||||
self.log_info("Начинается извлечение бронирований.")
|
|
||||||
reservations = Reservation.objects.filter(
|
|
||||||
Q(check_in__lte=self.end_time) & Q(check_out__gte=self.start_time)
|
|
||||||
)
|
|
||||||
self.log_info(f"Найдено {reservations.count()} бронирований для анализа.")
|
|
||||||
return reservations
|
|
||||||
except Exception as e:
|
|
||||||
self.log_error(f"Ошибка при извлечении бронирований: {e}")
|
|
||||||
return Reservation.objects.none()
|
|
||||||
|
|
||||||
def find_violations(self):
|
|
||||||
self.log_info("Начинается анализ несоответствий.")
|
|
||||||
user_logs = self.fetch_user_logs()
|
|
||||||
reservations = self.fetch_reservations()
|
|
||||||
|
|
||||||
log_lookup = {}
|
|
||||||
for log in user_logs:
|
|
||||||
params = parse_qs(log.url_parameters or "")
|
|
||||||
hotel_id = params.get("utm_content", [None])[0]
|
|
||||||
room_number = params.get("utm_term", [None])[0]
|
|
||||||
if hotel_id and room_number:
|
|
||||||
key = (hotel_id, room_number)
|
|
||||||
log_lookup.setdefault(key, []).append(log)
|
|
||||||
|
|
||||||
for reservation in reservations:
|
|
||||||
key = (reservation.hotel.hotel_id, reservation.room_number)
|
|
||||||
logs = log_lookup.get(key, [])
|
|
||||||
|
|
||||||
if reservation.status == "заселен" and not logs:
|
|
||||||
self.record_violation(
|
|
||||||
hotel=reservation.hotel,
|
|
||||||
room_number=reservation.room_number,
|
|
||||||
violation_type="no_qr_scan",
|
|
||||||
details=f"Бронирование для номера {reservation.room_number} в отеле '{reservation.hotel.name}' "
|
|
||||||
f"не имеет записи сканирования QR."
|
|
||||||
)
|
|
||||||
|
|
||||||
for log in logs:
|
|
||||||
if log.created < reservation.check_in:
|
|
||||||
self.record_violation(
|
|
||||||
hotel=reservation.hotel,
|
|
||||||
room_number=reservation.room_number,
|
|
||||||
violation_type="early_check_in",
|
|
||||||
details=f"Раннее заселение: сканирование QR {log.created} раньше времени заезда "
|
|
||||||
f"{reservation.check_in} для номера {reservation.room_number}."
|
|
||||||
)
|
|
||||||
|
|
||||||
for (hotel_id, room_number), logs in log_lookup.items():
|
|
||||||
matching_reservations = reservations.filter(
|
|
||||||
hotel__hotel_id=hotel_id,
|
|
||||||
room_number=room_number
|
|
||||||
)
|
|
||||||
if not matching_reservations.exists():
|
|
||||||
for log in logs:
|
|
||||||
self.record_violation(
|
|
||||||
hotel=Hotel.objects.filter(hotel_id=hotel_id).first(),
|
|
||||||
room_number=room_number,
|
|
||||||
violation_type="no_reservation",
|
|
||||||
details=f"Сканирование QR {log.created} для номера {room_number} в отеле с ID '{hotel_id}' "
|
|
||||||
f"не соответствует ни одному бронированию."
|
|
||||||
)
|
|
||||||
|
|
||||||
def record_violation(self, hotel, room_number, violation_type, details):
|
|
||||||
if hotel:
|
|
||||||
self.violations.append(ViolationLog(
|
|
||||||
hotel=hotel,
|
|
||||||
room_number=room_number,
|
|
||||||
violation_type=violation_type,
|
|
||||||
violation_details=details
|
|
||||||
))
|
|
||||||
self.log_warning(f"Зафиксировано нарушение: {details}")
|
|
||||||
|
|
||||||
def save_violations(self):
|
|
||||||
if self.violations:
|
|
||||||
ViolationLog.objects.bulk_create(self.violations)
|
|
||||||
self.log_info(f"Создано {len(self.violations)} записей в ViolationLog.")
|
|
||||||
else:
|
|
||||||
self.log_info("Нарушений не обнаружено.")
|
|
||||||
|
|
||||||
def run_check(self):
|
def run_check(self):
|
||||||
self.log_info(f"Запуск проверки с {self.start_time} по {self.end_time}.")
|
self.log_info(f"Запуск проверки.")
|
||||||
try:
|
try:
|
||||||
self.find_violations()
|
hotels_map = {}
|
||||||
self.save_violations()
|
hotels = Hotel.objects.all()
|
||||||
|
for hotel in hotels:
|
||||||
|
hotels_map[hotel.hotel_id] = hotel
|
||||||
|
|
||||||
|
user_logs = UserActivityLog.objects.filter(fraud_checked=False)
|
||||||
|
reservations = Reservation.objects.filter(fraud_checked=False).select_related('hotel')
|
||||||
|
missing = list(reservations)
|
||||||
|
|
||||||
|
violations = []
|
||||||
|
check_in_diff = timedelta(hours=self.checkin_diff_hours)
|
||||||
|
|
||||||
|
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
|
||||||
|
if reserv:
|
||||||
|
if reserv in missing:
|
||||||
|
missing.remove(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'
|
||||||
|
else:
|
||||||
|
v_type = 'no_booking'
|
||||||
|
|
||||||
|
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,
|
||||||
|
))
|
||||||
|
|
||||||
|
user_log.fraud_checked = True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(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
|
||||||
|
|
||||||
|
RoomDiscrepancy.objects.bulk_create(violations)
|
||||||
|
UserActivityLog.objects.bulk_update(user_logs, ['fraud_checked'], 1000)
|
||||||
|
Reservation.objects.bulk_update(reservations, ['fraud_checked'], 1000)
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_error(f"Ошибка при выполнении проверки: {e}")
|
self.log_error(f"Ошибка при выполнении проверки: {e}")
|
||||||
self.log_info("Проверка завершена.")
|
self.log_info("Проверка завершена.")
|
||||||
@@ -139,4 +108,4 @@ def run_reservation_check():
|
|||||||
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 завершена.")
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-02-01 09:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('antifroud', '0004_alter_roomdiscrepancy_discrepancy_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='roomdiscrepancy',
|
||||||
|
name='fraud_checked',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Проверено на несоответствия'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='useractivitylog',
|
||||||
|
name='fraud_checked',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Проверено на несоответствия'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-02-01 09:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('antifroud', '0005_roomdiscrepancy_fraud_checked_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='roomdiscrepancy',
|
||||||
|
name='fraud_checked',
|
||||||
|
field=models.BooleanField(db_index=True, default=False, verbose_name='Проверено на несоответствия'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='useractivitylog',
|
||||||
|
name='fraud_checked',
|
||||||
|
field=models.BooleanField(db_index=True, default=False, verbose_name='Проверено на несоответствия'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-02-01 09:48
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('antifroud', '0006_alter_roomdiscrepancy_fraud_checked_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='roomdiscrepancy',
|
||||||
|
name='fraud_checked',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -31,6 +31,7 @@ class UserActivityLog(models.Model):
|
|||||||
honeypot = models.BooleanField(verbose_name="Метка honeypot", blank=True, null=True)
|
honeypot = models.BooleanField(verbose_name="Метка honeypot", blank=True, null=True)
|
||||||
reply = models.BooleanField(verbose_name="Ответ пользователя", blank=True, null=True)
|
reply = models.BooleanField(verbose_name="Ответ пользователя", blank=True, null=True)
|
||||||
page_url = models.URLField(blank=True, null=True, verbose_name="URL страницы")
|
page_url = models.URLField(blank=True, null=True, verbose_name="URL страницы")
|
||||||
|
fraud_checked = models.BooleanField(default=False, verbose_name="Проверено на несоответствия", db_index=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def formatted_timestamp(self):
|
def formatted_timestamp(self):
|
||||||
|
|||||||
18
hotels/migrations/0004_reservation_fraud_checked.py
Normal file
18
hotels/migrations/0004_reservation_fraud_checked.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2025-02-01 09:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hotels', '0003_rename_external_id_hotel_external_id_pms_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='reservation',
|
||||||
|
name='fraud_checked',
|
||||||
|
field=models.BooleanField(db_index=True, default=False, verbose_name='Проверено на несоответствия'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -116,6 +116,7 @@ class Reservation(models.Model):
|
|||||||
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="Скидка")
|
||||||
|
fraud_checked = models.BooleanField(default=False, verbose_name="Проверено на несоответствия", db_index=True)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.check_out and self.check_in and self.check_out <= self.check_in:
|
if self.check_out and self.check_in and self.check_out <= self.check_in:
|
||||||
|
|||||||
18
tmp/tests.py
Normal file
18
tmp/tests.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import os
|
||||||
|
import django
|
||||||
|
import unittest
|
||||||
|
from antifroud.check_fraud import run_reservation_check
|
||||||
|
|
||||||
|
# Установка переменной окружения для Django settings
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_project.settings")
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
class FraudCheckTests(unittest.TestCase):
|
||||||
|
def test_run_reservation_check(self):
|
||||||
|
"""Тест проверки бронирования на мошенничество"""
|
||||||
|
result = run_reservation_check()
|
||||||
|
self.assertIsNotNone(result) # Проверяем, что результат не None
|
||||||
|
self.assertIsInstance(result, dict) # Проверяем, что возвращается словарь
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user