Compare commits

...

14 Commits

Author SHA1 Message Date
2ad382f01a ci/cd 2025-07-19 18:35:42 +09:00
67e487c378 ci/cd prepare 2025-07-19 18:34:51 +09:00
0cc3e469cb init commit 2025-02-26 22:35:18 +09:00
d771df32d5 remote changes 2025-02-26 22:29:39 +09:00
51569fb5c6 Merge branch 'zorn-dev' into PMSManager_refactor 2025-02-02 09:36:12 +09:00
6721ae3e8c xMerge branch 'zorn-dev' of ssh://git.smartsoltech.kr:2222/SmartSolTech/touchh_bot into zorn-dev 2025-02-02 09:28:20 +09:00
24f1a40561 RealtyCalendar plugin develop 2025-02-02 09:26:44 +09:00
zorn
d8bb7493e3 ReservationChecker 2025-02-01 22:03:25 +10:00
zorn
aca071f450 Fix migrations 2025-02-01 19:49:46 +10:00
zorn
6b98cda299 Add fraud_checked indexes 2025-02-01 19:38:47 +10:00
d826232dca Merge pull request 'zorn-dev' (#9) from zorn-dev into PMSManager_refactor
Reviewed-on: SmartSolTech/touchh_bot#9
2025-02-01 06:56:02 +00:00
05509f79fb init 2025-02-01 15:53:05 +09:00
157f47d86d some issues 2025-02-01 15:52:16 +09:00
e5e7a7f054 Merge pull request 'zorn-dev' (#8) from zorn-dev into PMSManager_refactor
Reviewed-on: SmartSolTech/touchh_bot#8
2025-01-29 07:53:46 +00:00
25 changed files with 644 additions and 1693 deletions

View File

@@ -12,20 +12,11 @@ steps:
- git reset --hard $DRONE_COMMIT - git reset --hard $DRONE_COMMIT
# Шаг 2: Обновление и запуск с помощью update.sh # Шаг 2: Обновление и запуск с помощью update.sh
- name: deploy_app - name: docker-build
image: docker:24 image: plugins/docker
environment: settings:
MYSQL_PASSWORD: touchh repo: trevor198507/touchh-py
volumes: dry_run: true
- name: docker_sock
path: /var/run/docker.sock
commands:
- apk add --no-cache bash
- chmod +x ./bin/update
- docker-compose up -d
- until docker inspect -f '{{.State.Running}}' src-web-1 | grep true; do echo "Waiting for container to be running..."; sleep 5; done
- git branch --set-upstream-to=origin/PMSManager_refactor PMSManager_refactor || true
- ./bin/update
# Шаг 3: Миграция базы данных # Шаг 3: Миграция базы данных
- name: run_migrations - name: run_migrations

View File

@@ -151,6 +151,7 @@ class UserActivityLogAdmin(admin.ModelAdmin):
get_hotel_name.short_description = "Отель" get_hotel_name.short_description = "Отель"
get_room_number.short_description = "Комната" get_room_number.short_description = "Комната"
# from .views import import_selected_hotels # from .views import import_selected_hotels
# # Регистрируем admin класс для ImportedHotel # # Регистрируем admin класс для ImportedHotel
# @admin.register(ImportedHotel) # @admin.register(ImportedHotel)
@@ -247,4 +248,3 @@ class RoomDiscrepancyAdmin(admin.ModelAdmin):
class Meta: class Meta:
model = RoomDiscrepancy model = RoomDiscrepancy
fields = ['hotel', 'room_number', 'booking_id','created_at', 'check_in_date_expected','check_in_date_actual','discrepancy_type'] fields = ['hotel', 'room_number', 'booking_id','created_at', 'check_in_date_expected','check_in_date_actual','discrepancy_type']

View File

@@ -1,27 +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 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 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.start_time = timezone.now() - timedelta(days=30)
self.end_time = timezone.now()
self.violations = []
self.checkin_diff_hours = 3
def log_info(self, message): def log_info(self, message):
logger.info(message) logger.info(message)
@@ -32,112 +154,106 @@ 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("🔍 Запуск проверки фродовых данных.")
try: try:
self.find_violations() check_in_diff = timedelta(hours=self.checkin_diff_hours)
self.save_violations()
# Кэшируем отели в словарь для быстрого доступа
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 = []
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("'", '"')) 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:
checked_reservations.add(reserv)
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})")
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 reserv in reservations:
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)
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: 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 завершена.")

View File

@@ -314,7 +314,7 @@ def scheduled_sync():
except Exception as e: except Exception as e:
logger.error(f"Error syncing connection {db_settings}: {e}") logger.error(f"Error syncing connection {db_settings}: {e}")
with ThreadPoolExecutor(max_workers=5) as executor: with ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(sync_task, db_settings) for db_settings in active_db_settings] futures = [executor.submit(sync_task, db_settings) for db_settings in active_db_settings]
for future in futures: for future in futures:
try: try:

View File

@@ -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='Проверено на несоответствия'),
),
]

View File

@@ -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',
),
]

View File

@@ -31,7 +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="Проверено на несоответствия") fraud_checked = models.BooleanField(default=False, verbose_name="Проверено на несоответствия", db_index=True)
@property @property
def formatted_timestamp(self): def formatted_timestamp(self):
@@ -79,10 +79,10 @@ class UserActivityLog(models.Model):
except AddressNotFoundError: except AddressNotFoundError:
return "IP-адрес не найден в базе" return "IP-адрес не найден в базе"
except FileNotFoundError: except FileNotFoundError:
logger.error(f"Файл базы данных GeoIP не найден по пути: {db_path}") # logger.error(f"Файл базы данных GeoIP не найден по пути: {db_path}")
return "Файл базы данных GeoIP не найден" return "Файл базы данных GeoIP не найден"
except Exception as e: except Exception as e:
logger.error(f"Ошибка при определении местоположения: {e}") # logger.error(f"Ошибка при определении местоположения: {e}")
return "Местоположение недоступно" return "Местоположение недоступно"
class ExternalDBSettings(models.Model): class ExternalDBSettings(models.Model):
name = models.CharField(max_length=255, unique=True, help_text="Имя подключения для идентификации.") name = models.CharField(max_length=255, unique=True, help_text="Имя подключения для идентификации.")
@@ -117,7 +117,6 @@ class RoomDiscrepancy(models.Model):
verbose_name="Тип несоответствия" verbose_name="Тип несоответствия"
) )
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
fraud_checked = models.BooleanField(default=False, verbose_name="Проверено на несоответствия")
def __str__(self): def __str__(self):
return f"{self.hotel.name} - Room {self.room_number}: {self.discrepancy_type}" return f"{self.hotel.name} - Room {self.room_number}: {self.discrepancy_type}"

View File

@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -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)

View 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='Проверено на несоответствия'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.1.4 on 2025-02-02 00:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0004_reservation_fraud_checked'),
]
operations = [
migrations.AlterField(
model_name='reservation',
name='check_in',
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата заезда'),
),
migrations.AlterField(
model_name='reservation',
name='check_out',
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата выезда'),
),
]

View File

@@ -111,11 +111,12 @@ 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="Скидка")
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:

View File

@@ -1,4 +1,144 @@
# # 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 os
import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
import requests import requests
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
@@ -11,20 +151,17 @@ class EcviPMSPlugin(BasePMSPlugin):
""" """
def __init__(self, hotel): def __init__(self, hotel):
super().__init__(hotel.pms) # Передаем PMS-конфигурацию в базовый класс super().__init__(hotel.pms)
self.hotel = hotel # Сохраняем объект отеля self.hotel = hotel
# Проверка PMS-конфигурации
if not self.hotel.pms: if not self.hotel.pms:
raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.") raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.")
# Инициализация параметров API
self.api_url = self.hotel.pms.url 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
# Настройка логгера
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
handler_console = logging.StreamHandler() handler_console = logging.StreamHandler()
handler_file = logging.FileHandler('var/log/ecvi_pms_plugin.log') handler_file = logging.FileHandler('var/log/ecvi_pms_plugin.log')
@@ -35,6 +172,10 @@ 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):
""" """
Возвращает настройки парсера по умолчанию. Возвращает настройки парсера по умолчанию.
@@ -47,7 +188,7 @@ class EcviPMSPlugin(BasePMSPlugin):
"room_type_name": "room_type", "room_type_name": "room_type",
"status": "occupancy", "status": "occupancy",
}, },
"date_format": "%Y-%m-%d %H:%M:%S" # Формат изменен на соответствующий данным "date_format": "%Y-%m-%d %H:%M:%S"
} }
async def _fetch_data(self): async def _fetch_data(self):
@@ -58,14 +199,23 @@ class EcviPMSPlugin(BasePMSPlugin):
data = {"token": self.token} data = {"token": self.token}
try: try:
# Запрос данных из PMS API
response = await sync_to_async(requests.post)( response = await sync_to_async(requests.post)(
self.api_url, headers=headers, json=data, auth=(self.username, self.password) self.api_url, headers=headers, json=data, auth=(self.username, self.password)
) )
response.raise_for_status() response.raise_for_status()
response_data = response.json() response_data = response.json()
self.logger.debug(f"Полученные данные с API: {response_data}") self.logger.debug(f"Полученные данные с API: {response_data}")
# Сохраняем весь ответ 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)
self.logger.info(f"API-ответ сохранен в файл: {file_path}")
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 {
@@ -80,22 +230,62 @@ class EcviPMSPlugin(BasePMSPlugin):
""" """
processed_items = 0 processed_items = 0
errors = [] errors = []
date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"]
unix_epoch = datetime(1970, 1, 1, 0, 0, 0)
date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"] # Поддержка нескольких форматов даты valid_reservations = []
print(data)
for item in data: for item in data:
try: try:
# Парсинг даты с поддержкой нескольких форматов checkin = item.get('checkin')
checkin = self._parse_date(item['checkin'], date_formats) checkout = item.get('checkout')
checkout = self._parse_date(item['checkout'], date_formats)
# Фильтруем записи с некорректными датами
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, 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,
} }
@@ -109,7 +299,7 @@ class EcviPMSPlugin(BasePMSPlugin):
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 {

View File

@@ -1,313 +1,111 @@
import logging
import requests import requests
import hashlib
import json import json
from .base_plugin import BasePMSPlugin import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from touchh.utils.log import CustomLogger
from hotels.models import Hotel, Reservation from hotels.models import Hotel, Reservation
from app_settings.models import GlobalHotelSettings from .base_plugin import BasePMSPlugin
from django.utils import timezone
class RealtyCalendarPlugin(BasePMSPlugin): class RealtyCalendarPMSPlugin(BasePMSPlugin):
def __init__(self, config): def __init__(self, hotel):
super().__init__(config) super().__init__(hotel.pms)
self.public_key = config.public_key self.hotel = hotel
self.private_key = config.private_key
self.api_url = config.url.rstrip("/") if not self.hotel.pms:
self.logger = CustomLogger(name="RealtyCalendarPlugin", log_level="DEBUG").get_logger() raise ValueError(f"Отель {self.hotel.name} не имеет связанной PMS конфигурации.")
if not self.public_key or not self.private_key:
raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar") self.api_url = self.hotel.pms.url.rstrip("/")
self.public_key = self.hotel.pms.public_key
self.private_key = self.hotel.pms.private_key
self.logger = logging.getLogger(self.__class__.__name__)
handler_console = logging.StreamHandler()
handler_file = logging.FileHandler('var/log/realtycalendar_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): def get_default_parser_settings(self):
"""
Возвращает настройки по умолчанию для обработки данных.
"""
return { return {
"date_format": "%Y-%m-%dT%H:%M:%S", "field_mapping": {
"timezone": "UTC" "check_in": "begin_date",
"check_out": "end_date",
"room_number": "apartment_id",
"room_type_name": "notes",
"status": "status",
},
"date_format": "%Y-%m-%d %H:%M:%S"
} }
def _get_sorted_keys(self, obj):
sorted_keys = sorted(obj.keys())
self.logger.debug(f"Отсортированные ключи: {sorted_keys}")
return sorted_keys
def _generate_data_string(self, obj):
sorted_keys = self._get_sorted_keys(obj)
string = "".join(f"{key}={obj[key]}" for key in sorted_keys)
self.logger.debug(f"Сформированная строка данных: {string}")
return string + self.private_key
def _generate_md5(self, string):
md5_hash = hashlib.md5(string.encode("utf-8")).hexdigest()
self.logger.debug(f"Сформированный MD5-хеш: {md5_hash}")
return md5_hash
def _generate_sign(self, data):
data_string = self._generate_data_string(data)
self.logger.debug(f"Строка для подписи: {data_string}")
sign = self._generate_md5(data_string)
self.logger.debug(f"Подпись: {sign}")
return sign
# async def _fetch_data(self):
# self.logger.debug("Начало выполнения функции _fetch_data")
# base_url = f"{self.api_url}/api/v1/bookings/{self.public_key}/"
# headers = {
# "Accept": "application/json",
# "Content-Type": "application/json",
# }
# now = datetime.now()
# data = {
# "begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"),
# "end_date": now.strftime("%Y-%m-%d"),
# }
# data["sign"] = self._generate_sign(data)
# try:
# response = requests.post(url=base_url, headers=headers, json=data)
# self.logger.debug(f"Статус ответа: {response.status_code}")
# if response.status_code != 200:
# self.logger.error(f"Ошибка API: {response.status_code}, {response.text}")
# raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}")
# try:
# response_data = response.json()
# bookings = response_data.get("bookings", [])
# # self.logger.debug(f"Полученные данные: {bookings}")
# if not isinstance(bookings, list):
# self.logger.error(f"Ожидался список, но получен: {type(bookings)}")
# raise ValueError("Некорректный формат данных для bookings")
# except json.JSONDecodeError as e:
# self.logger.error(f"Ошибка декодирования JSON: {e}")
# raise ValueError("Ответ не является корректным JSON.")
# except Exception as e:
# self.logger.error(f"Ошибка обработки ответа API: {e}")
# raise
# except Exception as e:
# self.logger.error(f"Ошибка запроса к API RealtyCalendar: {e}")
# raise
# try:
# hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config)
# hotel_tz = hotel.timezone
# self.logger.debug(f"Настройки отеля: {hotel.name}, Timezone: {hotel_tz}")
# hotel_settings = await sync_to_async(GlobalHotelSettings.objects.first)()
# check_in_time = hotel_settings.check_in_time.strftime("%H:%M:%S") if hotel_settings else "14:00:00"
# check_out_time = hotel_settings.check_out_time.strftime("%H:%M:%S") if hotel_settings else "12:00:00"
# except Exception as e:
# self.logger.error(f"Ошибка получения настроек отеля: {e}")
# check_in_time, check_out_time = "14:00:00", "12:00:00"
# filtered_data = []
# for item in bookings:
# try:
# if not isinstance(item, dict):
# self.logger.error(f"Некорректный формат элемента: {item}")
# continue
# reservation_id = item.get('id')
# if not reservation_id:
# self.logger.error(f"ID резервации отсутствует: {item}")
# continue
# begin_date = item.get('begin_date')
# end_date = item.get('end_date')
# if not begin_date or not end_date:
# self.logger.error(f"Отсутствуют даты в записи: {item}")
# continue
# checkin = timezone.make_aware(
# datetime.strptime(f"{begin_date} {check_in_time}", "%Y-%m-%d %H:%M:%S")
# )
# checkout = timezone.make_aware(
# datetime.strptime(f"{end_date} {check_out_time}", "%Y-%m-%d %H:%M:%S")
# )
# filtered_data.append({
# 'reservation_id': reservation_id,
# 'checkin': checkin,
# 'checkout': checkout,
# 'room_number': item.get('apartment_id'),
# 'room_type': item.get('notes', 'Описание отсутствует'),
# 'status': item.get('status')
# })
# except Exception as e:
# self.logger.error(f"Ошибка обработки элемента: {e}")
# # self.logger.debug(f"Отфильтрованные данные: {filtered_data}")
# await self._save_to_db(filtered_data)
async def _fetch_data(self): async def _fetch_data(self):
self.logger.debug("Начало выполнения функции _fetch_data") headers = {"Accept": "application/json", "Content-Type": "application/json"}
base_url = f"{self.api_url}/api/v1/bookings/{self.public_key}/"
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
}
now = datetime.now() now = datetime.now()
data = { data = {
"begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"), "begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"),
"end_date": now.strftime("%Y-%m-%d"), "end_date": now.strftime("%Y-%m-%d"),
"sign": self._generate_sign()
} }
data["sign"] = self._generate_sign(data)
try: try:
response = requests.post(url=base_url, headers=headers, json=data) response = await sync_to_async(requests.post)(
self.logger.debug(f"Статус ответа: {response.status_code}") f"{self.api_url}/api/v1/bookings/{self.public_key}/",
headers=headers, json=data
if response.status_code != 200: )
self.logger.error(f"Ошибка API: {response.status_code}, {response.text}") response.raise_for_status()
return {
"processed_intervals": 0,
"processed_items": 0,
"errors": [f"Ошибка API RealtyCalendar: {response.status_code}"]
}
response_data = response.json() response_data = response.json()
bookings = response_data.get("bookings", []) 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)]}
if not isinstance(bookings, list): async def _process_data(self, data):
self.logger.error(f"Ожидался список, но получен: {type(bookings)}")
return {
"processed_intervals": 0,
"processed_items": 0,
"errors": ["Некорректный формат данных для bookings"]
}
except json.JSONDecodeError as e:
self.logger.error(f"Ошибка декодирования JSON: {e}")
return {
"processed_intervals": 0,
"processed_items": 0,
"errors": ["Ответ не является корректным JSON."]
}
except Exception as e:
self.logger.error(f"Ошибка запроса к API RealtyCalendar: {e}")
return {
"processed_intervals": 0,
"processed_items": 0,
"errors": [str(e)]
}
# Получение настроек отеля
try:
hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config)
hotel_tz = hotel.timezone
self.logger.debug(f"Настройки отеля: {hotel.name}, Timezone: {hotel_tz}")
hotel_settings = await sync_to_async(GlobalHotelSettings.objects.first)()
check_in_time = hotel_settings.check_in_time.strftime("%H:%M:%S") if hotel_settings else "14:00:00"
check_out_time = hotel_settings.check_out_time.strftime("%H:%M:%S") if hotel_settings else "12:00:00"
except Exception as e:
self.logger.error(f"Ошибка получения настроек отеля: {e}")
check_in_time, check_out_time = "14:00:00", "12:00:00"
# Обработка записей
processed_items = 0 processed_items = 0
errors = [] errors = []
filtered_data = [] date_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"]
for item in bookings: for item in data.get("bookings", []):
try: try:
if not isinstance(item, dict): checkin = self._parse_date(item['begin_date'], date_formats)
raise ValueError(f"Некорректный формат элемента: {item}") checkout = self._parse_date(item['end_date'], date_formats)
reservation_id = item.get('id') reservation, created = await sync_to_async(Reservation.objects.update_or_create)(
if not reservation_id: reservation_id=item['id'],
raise ValueError(f"ID резервации отсутствует: {item}") defaults={
'room_number': item['apartment_id'],
begin_date = item.get('begin_date') 'room_type': item.get('notes', 'Описание отсутствует'),
end_date = item.get('end_date') 'check_in': checkin,
if not begin_date or not end_date: 'check_out': checkout,
raise ValueError(f"Отсутствуют даты в записи: {item}") 'status': item['status'],
'hotel': self.hotel,
checkin = timezone.make_aware( }
datetime.strptime(f"{begin_date} {check_in_time}", "%Y-%m-%d %H:%M:%S")
) )
checkout = timezone.make_aware(
datetime.strptime(f"{end_date} {check_out_time}", "%Y-%m-%d %H:%M:%S")
)
filtered_data.append({
'reservation_id': reservation_id,
'checkin': checkin,
'checkout': checkout,
'room_number': item.get('apartment_id'),
'room_type': item.get('notes', 'Описание отсутствует'),
'status': item.get('status')
})
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 {"processed_intervals": 1, "processed_items": processed_items, "errors": errors}
try:
await self._save_to_db(filtered_data)
except Exception as e:
self.logger.error(f"Ошибка сохранения данных в БД: {e}")
errors.append(f"Ошибка сохранения данных в БД: {str(e)}")
# Формирование отчета def _generate_sign(self):
report = { return hashlib.md5((self.public_key + self.private_key).encode("utf-8")).hexdigest()
"processed_intervals": 1, # Пример значения
"processed_items": processed_items,
"errors": errors
}
self.logger.debug(f"Сформированный отчет: {report}")
return report
@staticmethod
async def _save_to_db(self, data): def _parse_date(date_str, formats):
if not isinstance(data, list): for fmt in formats:
self.logger.error(f"Ожидался список записей, но получен {type(data).__name__}")
return
for index, item in enumerate(data, start=1):
try: try:
hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config) return datetime.strptime(date_str, fmt)
reservation_id = item.get('reservation_id') except ValueError:
if not reservation_id: continue
self.logger.error(f"Пропущена запись {index}: отсутствует 'id'") raise ValueError(f"Дата '{date_str}' не соответствует ожидаемым форматам: {formats}")
continue
existing_reservation = await sync_to_async(Reservation.objects.filter)(reservation_id=reservation_id)
existing_reservation = await sync_to_async(existing_reservation.first)()
defaults = {
'room_number': item['room_number'],
'room_type': item['room_type'],
'check_in': item['checkin'],
'check_out': item['checkout'],
'status': item['status'],
'hotel': hotel
}
if existing_reservation:
await sync_to_async(Reservation.objects.update_or_create)(
reservation_id=reservation_id, defaults=defaults
)
self.logger.debug(f"Резервация {reservation_id} обновлена. ")
else:
await sync_to_async(Reservation.objects.create)(
reservation_id=reservation_id, **defaults
)
self.logger.debug(f"Создана новая резервация {reservation_id}")
except Exception as e:
self.logger.error(f"Ошибка при обработке записи {index}: {e}")
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 m in required_methods: for method in required_methods:
if not hasattr(self, m): if not hasattr(self, method):
raise ValueError(f"Плагин {type(self).__name__} не реализует метод {m}.") raise ValueError(f"Плагин {type(self).__name__} не реализует метод {method}.")
self.logger.debug(f"Плагин {self.__class__.__name__} прошел валидацию.") self.logger.debug(f"Плагин {self.__class__.__name__} успешно прошел валидацию.")
return True return True

View File

@@ -1,6 +0,0 @@
def test_function():
"""тестовая функция для проверки планировщика
"""
print("Hello, World!")
return "Hello, World!"

View File

@@ -1,3 +1,9 @@
from django.test import TestCase from django.test import TestCase
# Create your tests here. # Create your tests here.
def test_function():
"""тестовая функция для проверки планировщика
"""
print("Hello, World!")
return "Hello, World!"

View File

@@ -0,0 +1,18 @@
.ml-4 {
margin-left: 1rem !important;
}
.ml-6 {
margin-left: 1.5rem !important;
}
.nav-sidebar ul.nav-treeview .nav-link {
padding-left: 2rem;
}
.nav-sidebar .nav-link {
display: flex;
}
.nav-sidebar .nav-link .nav-icon {
margin-top: .2rem;
margin-right: .3rem;
}

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

18
tmp/tests.py Normal file
View 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()

File diff suppressed because it is too large Load Diff