From 806c611cc740e0d6430c879dbe93b5c70f57b97f Mon Sep 17 00:00:00 2001 From: trevor Date: Tue, 10 Dec 2024 20:07:23 +0900 Subject: [PATCH] bnovo plugin scheduller --- .gitignore | 3 + 1db.sqlite3 | Bin 0 -> 327680 bytes 1db.sqlite3.sql | 149 +++++++++ bot.log | 0 bot/management/commands/run_bot.py | 75 ++--- bot/operations/hotels.py | 11 +- db2.sqlite3 | Bin 0 -> 335872 bytes db2.sqlite3.sql | 186 +++++++++++ hotels/admin.py | 24 -- hotels/booking_analyzer.py | 33 -- manage.py | 11 + pms_integration/admin.py | 47 +-- pms_integration/forms.py | 13 - pms_integration/manager.py | 18 +- ...0002_alter_pmsconfiguration_plugin_name.py | 18 ++ ...0003_alter_pmsconfiguration_plugin_name.py | 18 ++ ...0004_alter_pmsconfiguration_plugin_name.py | 18 ++ ...5_pmsconfiguration_private_key_and_more.py | 23 ++ pms_integration/models.py | 6 +- pms_integration/plugins/bnovo_pms.py | 185 +++++++---- pms_integration/plugins/realtycalendar_pms.py | 292 ++++++++++++++---- pms_integration/plugins/shelter_pms.py | 3 + pms_integration/test_requests.py | 73 +++++ requirements.txt | 22 +- scheduler/__init__.py | 0 scheduler/admin.py | 59 ++++ scheduler/apps.py | 6 + scheduler/migrations/0001_initial.py | 30 ++ scheduler/migrations/__init__.py | 0 scheduler/models.py | 47 +++ scheduler/static/scheduler/admin.css | 16 + scheduler/tasks.py | 66 ++++ .../scheduler/scheduledtasks/change_form.html | 42 +++ scheduler/test_module.py | 6 + scheduler/tests.py | 3 + scheduler/utils.py | 71 +++++ scheduler/views.py | 3 + touchh/settings.py | 1 + 38 files changed, 1301 insertions(+), 277 deletions(-) create mode 100644 1db.sqlite3 create mode 100644 1db.sqlite3.sql create mode 100644 bot.log create mode 100644 db2.sqlite3 create mode 100644 db2.sqlite3.sql delete mode 100644 hotels/booking_analyzer.py create mode 100644 pms_integration/migrations/0002_alter_pmsconfiguration_plugin_name.py create mode 100644 pms_integration/migrations/0003_alter_pmsconfiguration_plugin_name.py create mode 100644 pms_integration/migrations/0004_alter_pmsconfiguration_plugin_name.py create mode 100644 pms_integration/migrations/0005_pmsconfiguration_private_key_and_more.py create mode 100644 pms_integration/test_requests.py create mode 100644 scheduler/__init__.py create mode 100644 scheduler/admin.py create mode 100644 scheduler/apps.py create mode 100644 scheduler/migrations/0001_initial.py create mode 100644 scheduler/migrations/__init__.py create mode 100644 scheduler/models.py create mode 100644 scheduler/static/scheduler/admin.css create mode 100644 scheduler/tasks.py create mode 100644 scheduler/templates/admin/scheduler/scheduledtasks/change_form.html create mode 100644 scheduler/test_module.py create mode 100644 scheduler/tests.py create mode 100644 scheduler/utils.py create mode 100644 scheduler/views.py diff --git a/.gitignore b/.gitignore index 1bdca65a..935ee588 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ __pycache__ node_modules package-lock.json package.json +old_bot + +# Ignore files \ No newline at end of file diff --git a/1db.sqlite3 b/1db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..d3b8457e5dc34dd2dc8a0964949869962717de7d GIT binary patch literal 327680 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 literal 0 HcmV?d00001 diff --git a/1db.sqlite3.sql b/1db.sqlite3.sql new file mode 100644 index 00000000..214d86fe --- /dev/null +++ b/1db.sqlite3.sql @@ -0,0 +1,149 @@ +BEGIN TRANSACTION; +INSERT INTO "django_migrations" ("id","app","name","applied") VALUES (1,'contenttypes','0001_initial','2024-12-09 09:30:10.251024'), + (2,'auth','0001_initial','2024-12-09 09:30:10.285571'), + (3,'admin','0001_initial','2024-12-09 09:30:10.304106'), + (4,'admin','0002_logentry_remove_auto_add','2024-12-09 09:30:10.327893'), + (5,'admin','0003_logentry_add_action_flag_choices','2024-12-09 09:30:10.347643'), + (6,'contenttypes','0002_remove_content_type_name','2024-12-09 09:30:10.391061'), + (7,'auth','0002_alter_permission_name_max_length','2024-12-09 09:30:10.411017'), + (8,'auth','0003_alter_user_email_max_length','2024-12-09 09:30:10.433375'), + (9,'auth','0004_alter_user_username_opts','2024-12-09 09:30:10.456000'), + (10,'auth','0005_alter_user_last_login_null','2024-12-09 09:30:10.487091'), + (11,'auth','0006_require_contenttypes_0002','2024-12-09 09:30:10.490205'), + (12,'auth','0007_alter_validators_add_error_messages','2024-12-09 09:30:10.505490'), + (13,'auth','0008_alter_user_username_max_length','2024-12-09 09:30:10.531170'), + (14,'auth','0009_alter_user_last_name_max_length','2024-12-09 09:30:10.554633'), + (15,'auth','0010_alter_group_name_max_length','2024-12-09 09:30:10.572891'), + (16,'auth','0011_update_proxy_permissions','2024-12-09 09:30:10.595627'), + (17,'auth','0012_alter_user_first_name_max_length','2024-12-09 09:30:10.626279'), + (18,'users','0001_initial','2024-12-09 09:30:10.697462'), + (19,'hotels','0001_initial','2024-12-09 09:30:10.728173'), + (20,'pms_integration','0001_initial','2024-12-09 09:30:10.753819'), + (21,'hotels','0002_initial','2024-12-09 09:30:10.883078'), + (22,'hotels','0003_initial','2024-12-09 09:30:11.011514'), + (23,'sessions','0001_initial','2024-12-09 09:30:11.033336'); +INSERT INTO "django_admin_log" ("id","object_id","object_repr","action_flag","change_message","content_type_id","user_id","action_time") VALUES (1,'1','Shelter Golden Hills 3',1,'[{"added": {}}]',7,1,'2024-12-09 09:32:31.355706'), + (2,'2','Shelter Golden Hills 4',1,'[{"added": {}}]',7,1,'2024-12-09 09:33:10.530095'), + (3,'1','Golden Hills 3',1,'[{"added": {}}]',15,1,'2024-12-09 09:34:11.465732'), + (4,'2','Golden Hills 4',1,'[{"added": {}}]',15,1,'2024-12-09 09:34:21.783766'), + (5,'1','andrew',1,'[{"added": {}}]',18,1,'2024-12-09 09:35:20.367524'), + (6,'1','Настройки уведомлений для andrew',1,'[{"added": {}}]',19,1,'2024-12-09 09:35:40.518128'), + (7,'1','andrew - Golden Hills 3',1,'[{"added": {}}]',13,1,'2024-12-09 09:35:57.888800'), + (8,'2','andrew - Golden Hills 4',1,'[{"added": {}}]',13,1,'2024-12-09 09:36:06.799616'), + (9,'3','Как дома',1,'[{"added": {}}]',15,1,'2024-12-09 10:19:47.100883'); +INSERT INTO "django_content_type" ("id","app_label","model") VALUES (1,'admin','logentry'), + (2,'auth','permission'), + (3,'auth','group'), + (4,'auth','user'), + (5,'contenttypes','contenttype'), + (6,'sessions','session'), + (7,'pms_integration','pmsconfiguration'), + (8,'pms_integration','pmsintegrationlog'), + (9,'hotels','apiconfiguration'), + (10,'hotels','fraudlog'), + (11,'hotels','guest'), + (12,'hotels','reservation'), + (13,'hotels','userhotel'), + (14,'hotels','apirequestlog'), + (15,'hotels','hotel'), + (16,'users','localuseractivitylog'), + (17,'users','useractivitylog'), + (18,'users','user'), + (19,'users','notificationsettings'), + (20,'users','userconfirmation'); +INSERT INTO "auth_permission" ("id","content_type_id","codename","name") VALUES (1,1,'add_logentry','Can add log entry'), + (2,1,'change_logentry','Can change log entry'), + (3,1,'delete_logentry','Can delete log entry'), + (4,1,'view_logentry','Can view log entry'), + (5,2,'add_permission','Can add permission'), + (6,2,'change_permission','Can change permission'), + (7,2,'delete_permission','Can delete permission'), + (8,2,'view_permission','Can view permission'), + (9,3,'add_group','Can add group'), + (10,3,'change_group','Can change group'), + (11,3,'delete_group','Can delete group'), + (12,3,'view_group','Can view group'), + (13,4,'add_user','Can add user'), + (14,4,'change_user','Can change user'), + (15,4,'delete_user','Can delete user'), + (16,4,'view_user','Can view user'), + (17,5,'add_contenttype','Can add content type'), + (18,5,'change_contenttype','Can change content type'), + (19,5,'delete_contenttype','Can delete content type'), + (20,5,'view_contenttype','Can view content type'), + (21,6,'add_session','Can add session'), + (22,6,'change_session','Can change session'), + (23,6,'delete_session','Can delete session'), + (24,6,'view_session','Can view session'), + (25,7,'add_pmsconfiguration','Can add PMS система'), + (26,7,'change_pmsconfiguration','Can change PMS система'), + (27,7,'delete_pmsconfiguration','Can delete PMS система'), + (28,7,'view_pmsconfiguration','Can view PMS система'), + (29,8,'add_pmsintegrationlog','Can add Журнал интеграции PMS'), + (30,8,'change_pmsintegrationlog','Can change Журнал интеграции PMS'), + (31,8,'delete_pmsintegrationlog','Can delete Журнал интеграции PMS'), + (32,8,'view_pmsintegrationlog','Can view Журнал интеграции PMS'), + (33,9,'add_apiconfiguration','Can add Конфигурация API'), + (34,9,'change_apiconfiguration','Can change Конфигурация API'), + (35,9,'delete_apiconfiguration','Can delete Конфигурация API'), + (36,9,'view_apiconfiguration','Can view Конфигурация API'), + (37,10,'add_fraudlog','Can add Журнал мошенничества'), + (38,10,'change_fraudlog','Can change Журнал мошенничества'), + (39,10,'delete_fraudlog','Can delete Журнал мошенничества'), + (40,10,'view_fraudlog','Can view Журнал мошенничества'), + (41,11,'add_guest','Can add Гость'), + (42,11,'change_guest','Can change Гость'), + (43,11,'delete_guest','Can delete Гость'), + (44,11,'view_guest','Can view Гость'), + (45,12,'add_reservation','Can add Бронирование'), + (46,12,'change_reservation','Can change Бронирование'), + (47,12,'delete_reservation','Can delete Бронирование'), + (48,12,'view_reservation','Can view Бронирование'), + (49,13,'add_userhotel','Can add Пользователь отеля'), + (50,13,'change_userhotel','Can change Пользователь отеля'), + (51,13,'delete_userhotel','Can delete Пользователь отеля'), + (52,13,'view_userhotel','Can view Пользователь отеля'), + (53,14,'add_apirequestlog','Can add Журнал запросов API'), + (54,14,'change_apirequestlog','Can change Журнал запросов API'), + (55,14,'delete_apirequestlog','Can delete Журнал запросов API'), + (56,14,'view_apirequestlog','Can view Журнал запросов API'), + (57,15,'add_hotel','Can add Отель'), + (58,15,'change_hotel','Can change Отель'), + (59,15,'delete_hotel','Can delete Отель'), + (60,15,'view_hotel','Can view Отель'), + (61,16,'add_localuseractivitylog','Can add local user activity log'), + (62,16,'change_localuseractivitylog','Can change local user activity log'), + (63,16,'delete_localuseractivitylog','Can delete local user activity log'), + (64,16,'view_localuseractivitylog','Can view local user activity log'), + (65,17,'add_useractivitylog','Can add Журнал активности'), + (66,17,'change_useractivitylog','Can change Журнал активности'), + (67,17,'delete_useractivitylog','Can delete Журнал активности'), + (68,17,'view_useractivitylog','Can view Журнал активности'), + (69,18,'add_user','Can add Пользователь'), + (70,18,'change_user','Can change Пользователь'), + (71,18,'delete_user','Can delete Пользователь'), + (72,18,'view_user','Can view Пользователь'), + (73,19,'add_notificationsettings','Can add Способ оповещения'), + (74,19,'change_notificationsettings','Can change Способ оповещения'), + (75,19,'delete_notificationsettings','Can delete Способ оповещения'), + (76,19,'view_notificationsettings','Can view Способ оповещения'), + (77,20,'add_userconfirmation','Can add Подтверждение пользователя'), + (78,20,'change_userconfirmation','Can change Подтверждение пользователя'), + (79,20,'delete_userconfirmation','Can delete Подтверждение пользователя'), + (80,20,'view_userconfirmation','Can view Подтверждение пользователя'); +INSERT INTO "auth_user" ("id","password","last_login","is_superuser","username","last_name","email","is_staff","is_active","date_joined","first_name") VALUES (1,'pbkdf2_sha256$870000$0tWRKvUavKHjKmwWjWsfYc$mfqBdzr5TB74K1f9OHCI3w/66VZE7vY53MEpgUT73/4=','2024-12-09 09:31:26.675259',1,'trevor1985','','shadow85@list.ru',1,1,'2024-12-09 09:30:44.551380',''); +INSERT INTO "users_user" ("id","password","last_login","is_superuser","username","first_name","last_name","email","is_staff","is_active","date_joined","telegram_id","chat_id","role","confirmed") VALUES (1,'Andrey K. Tsoy','2024-12-09 09:34:41',1,'andrew','','','',0,1,'2024-12-09 09:34:27',556399210,556399210,'hotel_user',0); +INSERT INTO "users_notificationsettings" ("id","telegram_enabled","email_enabled","email","notification_time","user_id") VALUES (1,0,1,'a.choi@smartsoltech.kr','09:00:00',1); +INSERT INTO "hotels_hotel" ("id","name","created_at","api_id","pms_id") VALUES (1,'Golden Hills 3','2024-12-09 09:34:11.463934',NULL,1), + (2,'Golden Hills 4','2024-12-09 09:34:21.782571',NULL,2), + (3,'Как дома','2024-12-09 10:19:47.095457',NULL,NULL); +INSERT INTO "pms_integration_pmsconfiguration" ("id","name","url","token","username","password","plugin_name","created_at") VALUES (1,'Shelter Golden Hills 3','https://pms.frontdesk24.ru/sheltercloudapi/Reservations/ByFilter','A0DD4B7F-0381-4096-B46E-D22F1A571996',NULL,NULL,'Shelter PMS','2024-12-09 09:32:31.348604'), + (2,'Shelter Golden Hills 4','https://pms.frontdesk24.ru/sheltercloudapi/Reservations/ByFilter','A0DD4B7F-0381-4096-B46E-D22F1A571996',NULL,NULL,'Shelter PMS','2024-12-09 09:33:10.514492'); +INSERT INTO "pms_integration_pmsintegrationlog" ("id","checked_at","status","message","hotel_id") VALUES (1,'2024-12-09 09:36:43.044421','error','Плагин для PMS Shelter PMS не найден.',2), + (2,'2024-12-09 09:38:09.595349','error','Плагин для PMS Shelter PMS не найден.',1), + (3,'2024-12-09 09:40:38.721678','error','Плагин для PMS Shelter PMS не найден.',2), + (4,'2024-12-09 10:07:44.968856','error','Плагин для PMS Shelter PMS не найден.',1); +INSERT INTO "hotels_userhotel" ("id","hotel_id","user_id") VALUES (1,1,1), + (2,2,1); +INSERT INTO "django_session" ("session_key","session_data","expire_date") VALUES ('wjcybubh02eoyth1e4pm9cgjc8c9rk1v','.eJxVjEEOwiAQRe_C2hBgpgVcuvcMZIBBqoYmpV0Z765NutDtf-_9lwi0rTVsnZcwZXEWWpx-t0jpwW0H-U7tNss0t3WZotwVedAur3Pm5-Vw_w4q9fqthxwtITBY9MlGtJ6ADZiiAdyQ7KhVyVgcGY7gEL1BVqMhH51iNl68P9F6N0w:1tKa6w:zJT5PgcESbBBG5gKuJsyfV6EOxizdevxDzI4QrGbLsc','2024-12-23 09:31:26.682056'); +COMMIT; diff --git a/bot.log b/bot.log new file mode 100644 index 00000000..e69de29b diff --git a/bot/management/commands/run_bot.py b/bot/management/commands/run_bot.py index b79d0bea..0d042a30 100644 --- a/bot/management/commands/run_bot.py +++ b/bot/management/commands/run_bot.py @@ -5,63 +5,56 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from django.core.management.base import BaseCommand from telegram.ext import Application from bot.utils.bot_setup import setup_bot -from bot.utils.scheduler import setup_scheduler -from dotenv import load_dotenv -from bot.operations.users import show_users - -# Загрузка переменных окружения -load_dotenv() +from scheduler.tasks import load_tasks_to_scheduler class Command(BaseCommand): - help = "Запуск Telegram бота" + help = "Запуск Telegram бота и планировщика" def handle(self, *args, **options): - print("Запуск Telegram бота...") - - # Настройка Django окружения + # Установка Django окружения os.environ.setdefault("DJANGO_SETTINGS_MODULE", "touchh.settings") django.setup() - # Создание приложения Telegram + # Создаем новый цикл событий + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + # Настройка планировщика + scheduler = AsyncIOScheduler(event_loop=loop) + scheduler.start() + + # Загрузка задач в планировщик + try: + load_tasks_to_scheduler(scheduler) + except Exception as e: + self.stderr.write(f"Ошибка при загрузке задач в планировщик: {e}") + return + + # Настройка Telegram бота bot_token = os.getenv("TELEGRAM_BOT_TOKEN") if not bot_token: raise ValueError("Токен бота не найден в переменных окружения.") application = Application.builder().token(bot_token).build() - - # Настройка бота и обработчиков setup_bot(application) + # Основная асинхронная функция async def main(): - print("Настройка планировщика...") - scheduler = setup_scheduler() - scheduler.start() - + await application.initialize() + await application.start() + await application.updater.start_polling() + self.stdout.write(self.style.SUCCESS("Telegram бот и планировщик успешно запущены.")) try: - print("Инициализация Telegram бота...") - await application.initialize() # Инициализация приложения - print("Бот запущен. Ожидание сообщений...") - await application.start() # Запуск приложения - await application.updater.start_polling() # Запуск обработки сообщений - - # Бесконечный цикл для удержания приложения активным while True: - await asyncio.sleep(3600) # Ожидание 1 час - except Exception as e: - print(f"Ошибка во время работы бота: {e}") - finally: - print("Остановка Telegram бота...") - await application.stop() # Завершаем приложение перед shutdown - print("Остановка планировщика...") - scheduler.shutdown(wait=False) - print("Планировщик остановлен.") + await asyncio.sleep(3600) + except asyncio.CancelledError: + await application.stop() + scheduler.shutdown() + # Запуск асинхронной программы try: - asyncio.run(main()) - except RuntimeError as e: - if str(e) == "This event loop is already running": - print("Цикл событий уже запущен. Используем другой подход для запуска.") - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - else: - raise + loop.run_until_complete(main()) + except KeyboardInterrupt: + self.stdout.write(self.style.ERROR("Завершение работы Telegram бота и планировщика")) + finally: + loop.close() diff --git a/bot/operations/hotels.py b/bot/operations/hotels.py index bbac570d..7b5b55cd 100644 --- a/bot/operations/hotels.py +++ b/bot/operations/hotels.py @@ -138,18 +138,18 @@ async def check_pms(update, context): # Создаем экземпляр PMSIntegrationManager pms_manager = PMSIntegrationManager(hotel_id=hotel_id) - await sync_to_async(pms_manager.load_hotel)() + await pms_manager.load_hotel() await sync_to_async(pms_manager.load_plugin)() # Проверяем, какой способ интеграции использовать - if hasattr(pms_manager.plugin, 'fetch_data'): + if hasattr(pms_manager.plugin, 'fetch_data') and callable(pms_manager.plugin.fetch_data): # Плагин поддерживает метод fetch_data - data = await sync_to_async(pms_manager.plugin.fetch_data)() + data = await pms_manager.plugin.fetch_data() elif pms_config.api_url and pms_config.token: # Используем прямой запрос к API from pms_integration.api_client import APIClient api_client = APIClient(base_url=pms_config.api_url, access_token=pms_config.token) - data = await sync_to_async(api_client.fetch_reservations)() + data = api_client.fetch_reservations() else: # Если подходящий способ не найден await query.edit_message_text("Подходящий способ интеграции с PMS не найден.") @@ -163,7 +163,8 @@ async def check_pms(update, context): await query.edit_message_text(f"Интеграция PMS {pms_config.name} завершена успешно.") except Exception as e: # Обрабатываем и логируем ошибки - await query.edit_message_text(f"Ошибка: {str(e)}") + await query.edit_message_text(f"❌ Ошибка: {str(e)}") + async def setup_rooms(update: Update, context): """Настроить номера отеля.""" diff --git a/db2.sqlite3 b/db2.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..5ded71a76cc9f87b19058a0cf4e236b6b1ca27e6 GIT binary patch literal 335872 zcmeI53wRsZb>A@rNDw4}p&neUR;$&B5-q`8k~4S%vQ`UHkVJ_OiKL{}uD9?I7?O}c zfC2`3tm`_|?%Iiy)^+_gO`O_pUH41VrtNy;jn`hU(y~VDKWj+Fi$#|5)uM=H7G9J?D4sy)$=aFf)H~DywVKQlYr2>QcbgZxaOD zlage!*{10KIrD#k{(gr3chLWp56j!?Gi4jSa;}r49O8dxvXkPs#czs#Vr*iw187klS>rJmpG`CQLm=$UkV!}SxcQ{8{i{rPT{ zD&qqJAOHd&00JPeg9%)GtkZTdl_;fGv`jgt6=hitq|}_Q6;qYGjINfhrwVI&wvaES z)O;qjS}1CM*&iHH{3D7i$&t7ch=+os!9X+?j$S;_q|*PURfgliSUeCN4Tj}tI2L@A zR1Vcysid;`te#bKRzs+kKQ4zx!-0Stjy$!$(>AOo)>ca-t82?emAOD3hEkN5F65W8 z%Vi@owN}jDRCO(NUAxoL#XwvR#$|al7?gw2z@^D`|p2Tezt_2p*73HB$+rFX1N3<+bgY5sd}pSm=z0lw2~E z$T3q1cPOWB&!1>i4u<`w-K1!~q9|%r#I&&iv;~)FI2sN`&i9d?XDfOlrkE=zn0 zMViPmnUq#67K*7=>cQ%=R%(TaVii{8L@z0utSAeoirS5Gwy33O4$-x|uHRYHN-65i z{?;nwKtvwzArdSktl7o?CW_)~;*j{m;wQvg;`_u|&tDXO z+VjWaZ+iZg=a)Teo|zqdSOe2R00ck)1V8`;KmY_l00ck)1m0!>w3s}(xyt>A?8k-5 zx#Whk${l+}`-oti3{0`J5d5oSXs>-%pp%l;)3)rCt>r8(E$Ffbp?(f^Ey^Kik&V zV?XHPo6KBl$>Hv|A2MxcSzfz6P_flKXTr~oFsu4^+lMM74T=MTeXJtAnl;aUmmcdO z;Q>#tP5dMAUx@!sd|mt-;t|i6#D35J^ZbgsT38DLAOHd&00JNY0w4eaAOHd&00JPe6@l4FcKv^$+iw%UPFMf`sra(^ zN%2K-RlFje5rg6(v0oHCU-$f$=U;ig;Q5s2MNi)IUe6hi-*eF8aQ|2LFS&o({g>T0 z+;i@b`;ory^!;|<&-eYqz8~*YM6|^&RWm-TS|Kf2a4K_rB8msooFw7JHxW zJ=uGtx3}lpJ-^@c8$JJ|=Wq8s-=p>+O7I$0xUe@5JJL0M$2jR zi{rY>NApHedWG(*Y$%%ZI)pJ{fc-49uFA#z4k04!<;^r+a=LK9AsiR>nk_Y!&Kjj= zV~wRV+?2hny~d*HgAU=Ou!lF<>QdI!A%`#_>@k~dC2`)6Fq>{AaqcmPa6;I_nr~G+ zDLRCM!tScqXLmV-!@_Qhr)P{Di+86z4&i{XyW&xjJGiT=!g3Am)T0g|ChW3kvlLDk zg%-t@!ts3$At3Ckv|%YpaA^I!xmMThp5#dS%{E&}oHQiNhFeKI!J+lD)>{>y=ywQ* z1d*G-FZR7>w?h~bM6*qvb==4@8{t`T&RS$z`F4zVY!6pccR?}AiF!>hqoPk$RUgfZcAi%cEHHC#Dr)2_c(;3g1Zt8o~LlpKCaes zfm7yA^_hw-1!G2msoGL7I^Yn7g+8X-k~_i~_j2|8Lh0iiXs@Z5XAK)!rb?dmZqB%u zDdXEdZg~$^Q@Ol)j5pY0YO3TOHF8a5mE3pnCVH5@O4bmcC|q16fv&rxdW25}E>m@5 z>0zVPY@)GrkcXBomh$-3sXfGpN;hw^=~}PEJ8QSuW>fJ&qu6Y;srWJOO*d<`sq6qR zb5@$IGwD(8i?h~hUFm+KwAN@{=_A}zr`cv*(LT<*i#KUrk$#x>-!8KyGt+Binhltl z5Amt0i|I9U26!j!h|aHo5n6 z101GamLnQDrb3qE;j^cMX=0mh-p}n^g>e@?Ctx>q@T^`V%T&U%dU(rrriE|2xP2X5 zP3`h~H=hzZOkK5nr;%?etmSucsvS&gEwgjC!*x{Pnk(1v9eW+FVZl^e$+YuKQ)eZ! zqu=2g;(fZ3!=C>qi05qd4<8T!0T2KI5C8!X009sH0T2KI5CDNK39JVMw{87I!ga_! z;&LV2Ym3)2Oa4@8MWv^?9*IWiIfU{NS-&!O{^lk1=J~VN&ad9Oa_vfK>1z7O>e7wz z%yY%i!gwTjURjFGo}HKs+!_mqFF&1(+`JkJOefctFD*m@W5E;F=M2)LRAX^DIvVoF z=s&%v-7FN9STsb9W(v2Wp(k_Ml0I52SL(VZvh2aAOHd&00JNY0w4eaAOHd&00JQJfDyp@{{agq+yMa)009sH z0T2KI5C8!X009sHfffX?{%-*VSs(xcAOHd&00JNY0w4eaAOHd&@PHA(`u_n7DBJ-7 z5C8!X009sH0T2KI5C8!X0D%?+*z^AvJZ_u#9r0V@H^eu@uZh1a{(JE^#9tBrx%dm> z>*5#0&xwCe{M+KE#UB?xF8)RF!{WO5ez7bT#H^SWpB1l&7sc~pQamZf#DF*=9u*IX z`^5oK6kVd-^M5?w^8AVC4?X|I^B+B5_57OWUwMAf^N&5Rd0z4SL(fln{+8#Hp16!4H@I*X{XV^33Iq2Ev*+Yin0|Fob0w4ea zAOHd&00JNY0wD0#33S>8VPJu$^E{p7=|z_Ay};91p3d-enx%WDczT|v=Xg5F(%oly zdWNT`d75PDu2Vdn;ORI|6D;k2lBXwm`UFo;uvB~xPml97&eIr6JyD)UcpBzuh^6iz zPXj#l^HgDJpUl%So{sW#gr&WY^K_V}@8+qGr9H=ZdX%T{;^`1eT}OC&n5Tn0J;c&( ziKhp7`WR0Su+;e|Pxtfm5uWa2Y1hL%_44!~o({0Ib1zT#@N_p%cd^vb&r^}79-g{c zYVYG|FHd`T>SAd}H&2~B?c!;t!|oCsEU~ks!)|xEaQ*+S4;yFz0T2KI5C8!X009sH z0T2KI5CDPibpq`AKi>cMy&fgl2Ld1f0w4eaAOHd&00JNY0w4eaZ;b%f|8I>SYJ&g> zfB*=900@8p2!H?xfB*=9z)m25_5V(Y5^Mwk5C8!X009sH0T2KI5C8!X0D-qg0PFv^ z#t*eY00ck)1V8`;KmY_l00ck)1VCUX5WxC>CqxN0f&d7B00@8p2!H?xfB*=900@A< zTO;6h{!d%K@Tg5Z;`vqAZ*={<`&Qpay7qQnas1WZZ@Er(Cwt#x|0UrUN#KWU8@3Y- zZT0?9*NY8V_q|DWeEhyEzcUrrk3H)2rbdMIi`jfeyPdhF=9dd8HM5${r*ehmRJxGY zwY;9v@2qL5Y$lZsE~d4pw%91Re_}3~SV&5fGpCYINrR2WZS@RFvop=&gVM3VhL#6? z&;Nz}PVdNw@Zm9CUCe2XO*C#lW?Hn67@tbEZX%l*l(OXCvR0I4W*4NHOH)(Qg}KS; z#N1WseDbQ4xU?`kIYWx3lQRpCOM`{QYg$?-4ug`e-PU=9T6R%eD-KFG)na-@EgtjB zvahO@gw(X2E#y;6IdyqZDy^!y95pSK^QG)^UdvP^rHQl2iSy)to%|CgB-uwwNOOK! zORZ|9lDe!_tT0Vy<@MpRxW`WPI!AyiRW+A*|oveYL8e zHAg+^ zuhihoa#w}|S-rXV$O+pPJsA#kp-&Ca) z{^5S7_gON2dNbn#dFXs*~GL%w$e8Fz3Z-oaCfZM zV>lF^Si8K5s-dGQK{dP>4)`0!Z)5F;w;0Kd^3^e2vw+We*48W?jaP5f(J;j8G|@QU zv)AdpNHbNa!Eqjrl4XX9ghH`UFxc4N9%?Z>xC!kPG}>#(F)R5uK7Zbw>2`W!G2w+s zZAciUHS z7^y8)n0>r?Tx@nU&oymtG%-=LjV*ROO&pqSYT9Ae_;0Tb--V7Y@9?m2m4hwa$Ypgc zRnl&hwR~D@*tFLnY$!aIS68*isitrJs^IjFjR|*EuG3sht!9^tDqFUcnld`-^i~#p zwF>tY5p`|NvVaPO>K0JU*7l`THJ8C`SBCV;V(yc;~&IMJj z#Vh`3-KnIUTGH9=4cWZ)d=XtDr?Yq2QmRy@Gjhf$zr{i!SG@vFr7XApM3fS;T<4dJ z6R#w=lr2&ln~0JOH)C@VZB@y>Okfp8_ zvUJ;l)&1IC1iF^f=)~VDztpgpB}~Sx4mI!SJ_O@7h?b?rLe6@o&ccVBraKOZ%_?iV z&M@DL7i4}4@*}g=eqy+g)$g!iG4ypAz0JL*uKd0_4{wE?6s~wy!;-a}oK*PT9Y&8y z7d0ZwPh~LYkjF zt~F#ee~s*K>|x|{xtL3>snq*4oy@FP$gAjCy{>y%h_JhqYK>X!<0CCyDAUb8bRNGN z2*a-o5<)|yHzKBci%T=Tmm^??b0Rc%4s#n zx)~pz)Va;R-MXZwxs$gRd~3bE>V2)dSIp!0el>bm6Jx#C?BGqO-YJ}FCX;1X5Y$|X zid9QID|b2_J2yW&le(Oknmk2!JvE5?q}jO^N+#!dr}X)J%4nB2c~G$N>uKiY4Z5I2 zvm4#cma1J^QmS9OpMUp=(|hos@S(#zD%FDO7iT^5YW{s8{+`$yeXs6mw%kkl0*`lj z&m7t`zQ#33Bf$Lo!S!9k{E%aPwH9Gs!b8cwi7jHx3+^k(o9x8Rou+U7$ROQuG+cMb zQSHKC(+!gTrC=ni1&yoHH93!Iwt1f=_or6cR*U5_ZcUnbYQ6uDO~$_U`9n_conhgw zSi3@9tJ2g!nsyh=`IM}vi-AZ;tzDe1)#@^hZgEw*CbUh}+xCR!o{DjF1J#h^WLHdI z5NaKw)^r2K&K6yxCVXH0qY-m$Z(~lRZ`-|@RiCSe491~KZNjeatH#dCEcLCQJxI4$ z288wZ^XQvbee0~J3$3Y~x~Sz+{7@kk2`^>>nZP0se2WT>mcef!nA%jc5%(6=SUjpV z*=-2M5sYtr@-e435D@Ohc*tASY>{=KKJ+al?koH?``SjO;kc_VXg*q1C0eYkwx1OU zY&MIsgAwz77=P{iV8Fl4N=mKLq4oX)*1K*@g0&mUnJBxNB^?R}wM8}DDDbEmfp#~N zH`cG~7u9B1luXG%AFlszzoS4B1V8`;KmY_l00ck)1V8`;KmY`GDgmtjcWUHdHwb_L z2!H?xfB*=900@8p2!H?xY@Yzu|J!E;Ne}=55C8!X009sH0T2KI5C8!X*r^2A`hTBz z-6nop{GZ}K6@O0rqWCl7-xq&E{Hx+`iuc5y6n{wky7=G3e!3m00ck)1V8`;KmY_l00cnbfg|9syM%o#d6*?$mOR9g0ha7# z$sU&MX2~v=^s_`{iH9X_mh`ctmnA(caj~SEB~F%fv80nF4wl$i(qXr|TyCN{;OVi^ z%Kxjh-v6@rY4IbXCY}@zi=yZMdcNlQRnHeaf5-Dfo`UCno`mOJB#I9RfB*=900@8p z2!H?xfB*=9z>X&H$biFk@MtN$qGifCdgi_PC8MjQ>-+XPY~B(6Bzu;auc_kiFFLS? zibA|7U(mBlS>v@vB~91qoku12ZYmzA)_iyuZ4OsAE3eU^xBMLFuj*;_5+G4@*!Fo> z3iSG|5>LGz$~bCdsB77xcB4$M+95@I+*EMTC}0ioZv%aldDzG-YLt7E8!z_Ov@Wyy zyL%|(fFZY3RLdDs_=t=0MvOdai95esE^-A&x*fLRv9;AwioYbt$fQln7HK%_B&m^R zsXA4Ax*WCx`%7Ahy?&}>{@vf{uniqH-!Z}7;8e1F?{he80|V>@9n4R+owmGei@&O6 zcL!}AVw<(sbLR{MViz>)Gx4P1l6W)&0-9U+f-tUhUGT3O*nJ0w4ea-(v*UJ^Sc{?ml6C zR&XU0HJ>SJw>qAA{~*1XGQUhOOpHtKr&n-lxlD<^J~Q~?fZ`>3}MIJw-%R{h@d;Fd9?jP$U@LLn;Qi z4Z#*Q#ip8p$Y>}KibUo0UArmLUSa(TAqbA=3pWc}L|8GppE25u-fhGzQ;m}`9(ino zMuZJ%UNe~HBhJ}ilRqAajVe)pBodA7B5%BgH{7BI5Dit8Xgo;42uC6j|GK@O+@W{z z9VK@H8=u&CY2!=M#^*P_vhn4Omo{fORk>FdCEt!ANAmL)xYdZ4FAKF=YOz6U9VxC=4vzYjSST8N!cAIZ!YG0kdZd>k8WZjc z)RNND(!eHxWdT!+dBo_65gtNgelc>)IhW^$jG5Qyz(z+mgz7rFr5Ds$?vDo}@eqyc zNHiK#)+0SN%iA(;%jmmrc`!oE1O8~37E3gUTK2ex4ptoma!gX>xDq1*qx6cqXf$xX zo0J^qO8nJ9P~XEElvT$7DU)OIK$!J5ny0BBoOf20na2b)N7g9|Y@v+yZjsR_ePsE1 z7b$x`w@lgiv)x0KUQZtHq{j9cjkT;>J@ly7rCm!P90~b@;|{9pGZ5A}8*FIb z8U{v=_72KuBpQf>!{hd<4y8^Do6+3hTca-8Q7EHh85c(2fJ^95Y{+-`TbE8uV( zawXUuop+@3qtZgDaHrwE33gYUag#(-fj@G{_rI^P|M`y@QtVd)?E1eTe$z((@Bsl3 z009sH0T2KI5C8!X009sH0T6hg2)OL~T*gTPWBvc9Ht|m%C1V8`;KmY_l00ck)1V8`;KmY_lz-GhxAH4tsKmY_l00ck)1V8`;KmY_l00bU< z0&d~|+C`O@?KXPw?qNO*BVR~K^{ ze+FSGrLJX*+Kn>3t2b9zZj~3Em`f%WlF~wAd@3mowh|eXjtyorgHkrHYs>T!)|uG_ zY39<@lyqTkayl`0RXU%%DkUy0%uddbvgzc^0=+WYY&E54SG7SYqv{&_R;|k=N^4}d zmMZD0UM@B3x5|(Lbx_i^+q%*8#M$J;dFj}(bMvz^smqC}$y3LuNmC&g_DQpI%nWOu zG&#?!`+TIE(4?4Ki&=WpE2*t`HJ3b{oJ-D3B>Nio zIWxI1nV6coYGx)+`RLh?uQkLIn>&NR-9X6c9UBu~7~rGHGVYd(h4NadDdR}}IBF`t zuR+ALY}rR^O>M7RQrG7`aK%ky+S>covc8hyvfI<_3k18oXATKAK3A1)`ABhnV7IX*|4+V$cn3c?lN{9*-R>$4o4R=i~d?DtYWeWc5>!a@+re?BX(Ay zCS8Nl>`Xgh76jH7>vz8G@7GebeH_(H$&0Z7y_H_8S>3cJ9BXHKlVls+jb1d>W3JSh zMGn2^38z=19rd#f?3*eZ8RUvzR?})Q7}>;?(e1lpqOEG(mJ_vjy#GYEcfEH)xEt8E z{dELV!9XAoO$S4%az1-w6PHFzmoCjrUc8i)n)+9JUYoS{rktfwSf`4D=Y5aUJ3l6@ z#~PhCW_x22WZf^OE~TRx+1mXKk;mIcs-21^n+-wMxA-?>oa{aB^j;*(LTxOoggvwr zUJ9u(*+SmXH@uZ$jT$zw%M>MG6LF_^mCU-eg;^C(Qj5{3rY=VPjorgA^4(i?3PY?q zbXu$3hUM0spYQHi%;}BAgy-)x?BuO;Tn(r-*Jm1R9yYfYy06`iwUt!5kfD7L-vI^u zb-S2!QDeJ-l&ZJb6PWuM)`K=ZysepSn!42y?R>P$yPVj32cIwK*`;in?`%q%u4nVh zrIcbc)vD~+-RB}sZz3VQ@Igx$T1mFoakN!9TB*3Nkmy=YTP~`rDJ{>IRt##Pkh2~Q zXsc>Aw?(mapB|)G8hd=Jk>+BlaiPTAZW&5*3bh`xz3GVFeJ1SmPD}_doUp*>E%Kx` zQCbgq8;gco;cuzrzW8%ZZ+BmA+6y$VZ>?bYv2cyXHph$c{=cu<#INp*We=o|F^}10uTTJ5C8!X009sH0T2KI5C8!XcpwO1{r^CO z5l(;r2!H?xfB*=900@8p2!H?xfWW2%u>Ri^4+=p51V8`;KmY_l00ck)1V8`;K;VHO zfc5_a5k@!x0w4eaAOHd&00JNY0w4eaAOHfJ62SU@Q#>dH0T2KI5C8!X009sH0T2KI z5CDM(f&kY44@4N@1PFit2!H?xfB*=900@8p2!H?xY)Sy@|4s3r5ClK~1V8`;KmY_l z00ck)1V8`;9tZ+%=0*Pl;R>7p0T2KI5C8!X009sH0T2KI5C8!XXhVQq|96OgU=zP1 zepCEo`hpJ#fB*=900@8p2!H?xfB*=900@A<4kplP7X*icrFJ_@J38!kmkam*@8FQa zbPxam5C8!X009sH0T2KI5C8!X*d_t2|F;PWVjutlAOHd&00JNY0w4eaAOHd&u!9L; z{l9~Q2h%|S1V8`;KmY_l00ck)1V8`;Kwz5$@ch4Rf`S+bfB*=900@8p2!H?xfB*=9 z00``00_^%fp8vOl0|?VW00ck)1V8`;KmY_l00ck)1VEsUKFZi16$?bwbb4FHCfXa@tnE!$TdAz-Wbw$X zVexE}#VZ9}%cZg@HKPSH>b5L?vc1L4+TMi4X5|dodu7JyEfL{6jrO*VPO4Hhr7UK` z895WS#JqKtlWm=7E#KV4eR+2GebY|w?5yy@n6577v@OltQs4>GkA=keRC1dgIhz@j z_=wSp(#-6FG;?WcO1dyNIh~lhDxFVWl@ga0W+!Jz^K^1%;c;ou(wC%+s%v_7Rjbyd za&u%zH`QW#MJ*l+$v$iGs#Yqg%i5r%YqxdY8r7+UY*1RvE|WFY3esHibaE~^Gm)G( zrAh{THU`ObpLB`|%yEY&XC@aW6H`-H&CKK}pX-+J^&K$d!Nj+RK;&GP_xzAx<1I*7c%L)3j(=dm=}is^>j$``wTSIeXeLahL%~=qz2vu+`RnHm zYf)o0V}h_supVsa9`v1<@A6(8-ooysqFT<-9<6G6CF7Lm1H#JN0}xs^Bd~tpywiIr zB&?sXb`y0iThwlpwUW+)LtCu7EX9KUOf)8&5wDYZPs?soC(@{vt2@B6C`Ob9;omx=QnTWxn@^LUj{YB0cTw3g0@1r5#-P?0c z@9?nj;!$e|DkIfIUD=#y8Hh^heZ``vm9*kbj)uBn^ZwKzOFWiYhfTE#9R+~O`EH($ zyHji*Z$z%TSEk~OrfccV!rHK(H;*i+CTGynHfuOqbjq#v^){RE{AQyUW$!ovcKzQW zp0S zMV91X+#iYuBBP-|Fy@aqWP6uwn8Y?dvGLNzm!yr)Z+vCr%Ns9MYREB3k>g4%9*m62 zu~0A+v5$3h*?d&vOd*%i^3vICE?1I*mI`t#9-vl~(MZ%EiYOhU0^0G7>v z|L^&TP5g|Q^n8uJ-~$3600JNY0w4eaAOHd&00JNY0`Dk+XXrQd3GW@3<+tkeYj`;l z4+ckL;b=4z){4bKapTh)uhFmPe`e#=jn~;P=wG|{QR%|;yfnX}<#c)?GW)*q`o=3P zd1>Q|B=O3|>!a_bU*3~)!J?dgIUkhcf#_(&uY@Dfx1hY^8TyregX!#d`O&x>8x6(S zkN@9-_V>~+`kPG;gyX@$s2mIi{mNTV-eLUy{|{~AH^n~`zb*dpI~rmr2LTWO0T2KI z5C8!X009sH0T2KI5O}8vIP5OL(POCisY5>qfB*=900@8p2!H?x zfB*=900?|{32d_df6*rXC-E1=&x$X8cTGZTAOHd&00JNY0w4eaAOHd&00JQJeMI0f zd&2t5god9zI2wu@ez;(7F1G&ipyPl&Vf{@7`(c9Rw*$ug|6jGy+yCAW|Fihj?<4AP z6$C&41V8`;KmY_l00ck)1V8`;c0K`@-7D}1130SR?A31_-FB~wm$K*o+4^_ByYK)6 zKmY_l00ck)1V8`;KmY_l00cmwJpp$8AM5}2d{6=cAOHd&00JNY0w4eaAOHd&00IvJ z0bKuo5TXblKmY_l00ck)1V8`;KmY_l00cl_a{{>jzd0Y2f&d7B00@8p2!H?xfB*=9 z00@A`#Y7aM)=5?>uSi@949MdH2WMQ+@xScco{;_19fp-6iMmI?r@XIy*XF zw!c9Fv)gjrdD8x=Z3#l?dxe1Wr0|D^EzkeCqSJf)xbUHQU0uv+rSyuHDd)6es`4$P ztEKCejfU(K6LZPLLQ+~tj87${!8THZ(y_s8W>CuJb!}NIN;9(y(#)l)De1!80 zs&qbiRZ3i1n4O#YAS88G*S+4WomZn`deAKnudbJH{;%su_ymai?x%t_d)aAs~F>w|IcpR!b=!h@xuPvRX`NjH>ZzXRqP^50!s&2ZdQ7_D-RNdMi&3;U} z*K+0MY`$guP4&g>P12CMuH9+f!diJTmrb`R_pR^EIKA^eVLg`3XSCayYifSEkSb}V z66;Ue?X_%COR<5HQbXymKO7F!i?D&k2I}O@spL~Mgto3fD9z3^3bJ5Y+8Xq&hZmh* zpHH}Zl=rQAwe?$5_5G&4kSVS>Q%5Lh85-8NqhdB(|eIj3bi)LFpO+kj6|1${;0pvrpOjHH7RIsR7G^q zcUO7V=^YsnUKrp*sjO_ay_bd> z2I4Xwfkln9(bUW4Ns6VCQ8iVKW=!k3Ot57fH?3Y5NiNAut#6BBIdRqL9U2l|n6$={ zi(A_dw~S#`>zj^WtDRhJSGAZe>MLwlVeH8&M^$Spg}mk1CB!CPW7nmvs#)tEHx#TE zH8{L!R85a9Rkj=5{ZCPJm4@hA^rX0I7TrKv3HcZOi_L9?TE=)&C3P{jNHVi4Ee`r7 zu5@{?DDCzn&4#3u=DgF-KYiKhRTSYvDQicuwAoxI*|O7Es+e@&+)=lse#>EcX=d`` zr6djHVxh2_%9mFcwPNjfhIg7;PvbKBoi$6hYCKw})kgX{9gns?sxnFo!wtK>rJ`ES z&@n)*^F|`!SSGCo%~)3i0xf%PrL?(vUAL`>)%RKa{@+;pA_V~u009sH0T2KI5C8!X z009sHfd`oY*8dN3bm1EafB*=900@8p2!H?xfB*=900!(B3)y{Cr~dIsYvsdNcE!S_nn_rIJ5;y%^G$ugSOk z#p2@9(x`Us_T_8IhD+qd+|h4HJg>D(FpTsU#+>1;NUxpOgce&zC=%ggCAS0l^G zDP{cfjp>!MAtgJL3r8=+PKRgYTX97{Up^I%(+_5np(~Tg+11GGbMteHlebE8`Hreb zrvjI+UAS{?c6?!6JDdh5~^|G;(*agF||5WodP# ztQT+i3;sx?w3^W}VL7w5c)MI&xpSjL4e(LJ<>m`(Yu2Jhji;&`p#l`aYgoP zg**C+q6ODhW9j8<>1aAuysqp>NNV_2{JC=rp$p6DQ8?+)t3AemN9o*Z=L}U$KdQMf?xq7ruML94&zW z2!H?xfB*=900@8p2!H?xfWY@Rfg|>5VXKp0b|$pd$)AqH_G#BvCqIn!|7UIDe-eK| z{OtEPd3X;3AOHd&00JNY0w4eaAOHd&00MuO1Rk>|gz6Rirh7r`jm7nML3A9jCtTIq zP1pa~{r{7;pRm!@|Chxd6c@#~*ys6e&*wcq>M43=Jfoi7?mu?_Yxhg;7u^f)!+qcE z`}Mw`?t7u{eSPol{iEKOd!Or-dw!?qFZZ14ak#$Xy6p;g|Bvp!+pTs#>il2M4d+if z?>f&rJze*@eyr=MuHBvgvhyRIXFF|MT=-Mr zW#O(cB|K#NJu>OcWT#Cyk=mnXGO2t)&n{)t{MoG~P1m#e<l%jDG z=4PcK?p7PyPMsq8kh-ht4Sx)MR=-0}kgPfOqxYV__XFhqOB=6AlzfRidzGlX&Oov6 zXD3KYOxtDgut877*)27V8=5R$H)yH=*h8bd)m7XUb6w-1u?PfgcRB#qs*N@1|irGF+ z!}2KNS`Yac>kxc`XMbO&E<%(2#w*9isG>68XF_PGTEWn+@~BbS^u3{S#rrmuLnBmq zc)E|d+)$G{-KN^%$En((Kri={Kk2*X6ukg#kuV%N(_;rq3b=&bE_8a{Pt8ld4yC%uDCcE z8t!$kO{o$31nZ#pUZ8IIGkkaa(!CF{;}JH0ojOb!jz8lvv9!`r!Lyl`K|_nlq?MKm zn@zMlafq~pF1Z+xRw_7{P3oVMsD3Ql&1ukEU@Y&HX@5-eDv@DFTR%g?m~+{9Rk|=e zKXZ_DomlNQxwO<)0aF*NcKRMO^qGWO>Z>5yj=r-8NMB^Ro3U!CthpDpu`K*3(=_3% zP;1?X2#G|8wy*Nx^vcGUH(rYGr+UZFIBSHOYMOvr)P2OLTcgob*M!krH@J`Lh7wNG z^QMZ-@#b2C4^yq7kuL6~`JR^Q05P}U;cdzBSlOuU*0kbkmcRR`YDz8ZaVJTRMLJA- zY6VQzG^&>Qc$XnFSzVPmql&pMYpP-{&a732FH)7U z&;VPYHOaG8npOCz3te=&XuWeUcY!U&>9oL7wz$f#iPpSer`A8uPrbfEXV!j}JmObD zH(rtWh0*4VogaPgENMQz?%Hd5+e&xE;rq}&V`?|Sw9;N-^X9c*o+j;~b^Bh%u$5|# z@y+XUnui#=v-HV~I1aXC2ZSE9jKtiG-q2!~^8AQ)SW#S|sI6o~}2 zbUqEj8b_V_o?DW&K|3(ABXjl%K>~`=tf$1Os0w4eaAOHd&00JNY0w4eaAOHf} HB=G+Mls_2q literal 0 HcmV?d00001 diff --git a/db2.sqlite3.sql b/db2.sqlite3.sql new file mode 100644 index 00000000..a1138d4b --- /dev/null +++ b/db2.sqlite3.sql @@ -0,0 +1,186 @@ +BEGIN TRANSACTION; +INSERT INTO "django_migrations" ("id","app","name","applied") VALUES (1,'contenttypes','0001_initial','2024-12-10 01:46:55.727280'), + (2,'auth','0001_initial','2024-12-10 01:46:55.748342'), + (3,'admin','0001_initial','2024-12-10 01:46:55.761857'), + (4,'admin','0002_logentry_remove_auto_add','2024-12-10 01:46:55.771717'), + (5,'admin','0003_logentry_add_action_flag_choices','2024-12-10 01:46:55.783546'), + (6,'contenttypes','0002_remove_content_type_name','2024-12-10 01:46:55.798657'), + (7,'auth','0002_alter_permission_name_max_length','2024-12-10 01:46:55.812626'), + (8,'auth','0003_alter_user_email_max_length','2024-12-10 01:46:55.826016'), + (9,'auth','0004_alter_user_username_opts','2024-12-10 01:46:55.836404'), + (10,'auth','0005_alter_user_last_login_null','2024-12-10 01:46:55.846555'), + (11,'auth','0006_require_contenttypes_0002','2024-12-10 01:46:55.850370'), + (12,'auth','0007_alter_validators_add_error_messages','2024-12-10 01:46:55.858610'), + (13,'auth','0008_alter_user_username_max_length','2024-12-10 01:46:55.868657'), + (14,'auth','0009_alter_user_last_name_max_length','2024-12-10 01:46:55.880462'), + (15,'auth','0010_alter_group_name_max_length','2024-12-10 01:46:55.893095'), + (16,'auth','0011_update_proxy_permissions','2024-12-10 01:46:55.900517'), + (17,'auth','0012_alter_user_first_name_max_length','2024-12-10 01:46:55.911606'), + (18,'users','0001_initial','2024-12-10 01:46:55.941858'), + (19,'hotels','0001_initial','2024-12-10 01:46:55.957904'), + (20,'pms_integration','0001_initial','2024-12-10 01:46:55.968957'), + (21,'hotels','0002_initial','2024-12-10 01:46:56.002327'), + (22,'hotels','0003_initial','2024-12-10 01:46:56.025083'), + (23,'sessions','0001_initial','2024-12-10 01:46:56.034581'), + (24,'pms_integration','0002_alter_pmsconfiguration_plugin_name','2024-12-10 02:52:26.722876'), + (25,'pms_integration','0003_alter_pmsconfiguration_plugin_name','2024-12-10 02:58:35.733611'), + (26,'pms_integration','0004_alter_pmsconfiguration_plugin_name','2024-12-10 03:00:06.725614'), + (27,'pms_integration','0005_pmsconfiguration_private_key_and_more','2024-12-10 03:04:00.440483'), + (28,'scheduler','0001_initial','2024-12-10 06:42:05.633067'), + (29,'scheduler','0002_alter_scheduledtask_options_and_more','2024-12-10 06:49:38.460869'), + (30,'scheduler','0003_alter_scheduledtask_options_and_more','2024-12-10 07:13:54.438968'); +INSERT INTO "django_admin_log" ("id","object_id","object_repr","action_flag","change_message","content_type_id","user_id","action_time") VALUES (1,'1','Shelter Golden Hills 3',1,'[{"added": {}}]',7,1,'2024-12-09 09:32:31.355706'), + (2,'2','Shelter Golden Hills 4',1,'[{"added": {}}]',7,1,'2024-12-09 09:33:10.530095'), + (3,'1','Golden Hills 3',1,'[{"added": {}}]',15,1,'2024-12-09 09:34:11.465732'), + (4,'2','Golden Hills 4',1,'[{"added": {}}]',15,1,'2024-12-09 09:34:21.783766'), + (5,'1','andrew',1,'[{"added": {}}]',18,1,'2024-12-09 09:35:20.367524'), + (6,'1','Настройки уведомлений для andrew',1,'[{"added": {}}]',19,1,'2024-12-09 09:35:40.518128'), + (7,'1','andrew - Golden Hills 3',1,'[{"added": {}}]',13,1,'2024-12-09 09:35:57.888800'), + (8,'2','andrew - Golden Hills 4',1,'[{"added": {}}]',13,1,'2024-12-09 09:36:06.799616'), + (9,'3','Как дома',1,'[{"added": {}}]',15,1,'2024-12-09 10:19:47.100883'), + (10,'2','Shelter Golden Hills 4',2,'[{"changed": {"fields": ["\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430"]}}]',7,1,'2024-12-10 02:47:47.763286'), + (11,'1','Shelter Golden Hills 3',2,'[{"changed": {"fields": ["\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u0430"]}}]',7,1,'2024-12-10 02:47:51.978891'), + (12,'2','Shelter Golden Hills 4',2,'[{"changed": {"fields": ["plugin_name"]}}]',7,1,'2024-12-10 03:00:54.761679'), + (13,'2','Shelter Golden Hills 4',2,'[]',7,1,'2024-12-10 03:01:06.448068'), + (14,'3','Как дома / RealtyCalendar',1,'[{"added": {}}]',7,1,'2024-12-10 03:07:04.219584'), + (15,'3','andrew - Как дома',1,'[{"added": {}}]',13,1,'2024-12-10 03:07:13.403477'), + (16,'3','Как дома',2,'[{"changed": {"fields": ["PMS \u0441\u0438\u0441\u0442\u0435\u043c\u0430"]}}]',15,1,'2024-12-10 03:16:16.331411'), + (17,'4','Bnovo',1,'[{"added": {}}]',7,1,'2024-12-10 04:18:40.567772'), + (18,'4','Bnovo',2,'[{"changed": {"fields": ["\u041b\u043e\u0433\u0438\u043d", "\u041f\u0430\u0440\u043e\u043b\u044c"]}}]',7,1,'2024-12-10 04:22:39.182778'), + (19,'4','Test',1,'[{"added": {}}]',15,1,'2024-12-10 04:25:37.535780'), + (20,'4','andrew - Test',1,'[{"added": {}}]',13,1,'2024-12-10 04:25:43.910574'), + (21,'1','bot running',1,'[{"added": {}}]',21,1,'2024-12-10 07:04:41.982258'), + (22,'2','bot.check_pms',1,'[{"added": {}}]',21,1,'2024-12-10 07:07:20.515061'), + (23,'2','bot.check_pms',2,'[{"changed": {"fields": ["\u0424\u0443\u043d\u043a\u0446\u0438\u044f", "\u0410\u043a\u0442\u0438\u0432\u043d\u043e", "\u0414\u043d\u0438 \u043d\u0435\u0434\u0435\u043b\u0438"]}}]',21,1,'2024-12-10 08:14:49.799413'), + (24,'1','bot running',2,'[{"changed": {"fields": ["\u0424\u0443\u043d\u043a\u0446\u0438\u044f", "\u0414\u043d\u0438 \u043d\u0435\u0434\u0435\u043b\u0438", "\u0414\u043d\u0438 \u043d\u0435\u0434\u0435\u043b\u0438"]}}]',21,1,'2024-12-10 08:15:52.093648'), + (25,'1','andrew',2,'[{"changed": {"fields": ["\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d"]}}]',18,1,'2024-12-10 08:35:12.673349'); +INSERT INTO "django_content_type" ("id","app_label","model") VALUES (1,'admin','logentry'), + (2,'auth','permission'), + (3,'auth','group'), + (4,'auth','user'), + (5,'contenttypes','contenttype'), + (6,'sessions','session'), + (7,'pms_integration','pmsconfiguration'), + (8,'pms_integration','pmsintegrationlog'), + (9,'hotels','apiconfiguration'), + (10,'hotels','fraudlog'), + (11,'hotels','guest'), + (12,'hotels','reservation'), + (13,'hotels','userhotel'), + (14,'hotels','apirequestlog'), + (15,'hotels','hotel'), + (16,'users','localuseractivitylog'), + (17,'users','useractivitylog'), + (18,'users','user'), + (19,'users','notificationsettings'), + (20,'users','userconfirmation'), + (21,'scheduler','scheduledtask'); +INSERT INTO "auth_permission" ("id","content_type_id","codename","name") VALUES (1,1,'add_logentry','Can add log entry'), + (2,1,'change_logentry','Can change log entry'), + (3,1,'delete_logentry','Can delete log entry'), + (4,1,'view_logentry','Can view log entry'), + (5,2,'add_permission','Can add permission'), + (6,2,'change_permission','Can change permission'), + (7,2,'delete_permission','Can delete permission'), + (8,2,'view_permission','Can view permission'), + (9,3,'add_group','Can add group'), + (10,3,'change_group','Can change group'), + (11,3,'delete_group','Can delete group'), + (12,3,'view_group','Can view group'), + (13,4,'add_user','Can add user'), + (14,4,'change_user','Can change user'), + (15,4,'delete_user','Can delete user'), + (16,4,'view_user','Can view user'), + (17,5,'add_contenttype','Can add content type'), + (18,5,'change_contenttype','Can change content type'), + (19,5,'delete_contenttype','Can delete content type'), + (20,5,'view_contenttype','Can view content type'), + (21,6,'add_session','Can add session'), + (22,6,'change_session','Can change session'), + (23,6,'delete_session','Can delete session'), + (24,6,'view_session','Can view session'), + (25,7,'add_pmsconfiguration','Can add PMS система'), + (26,7,'change_pmsconfiguration','Can change PMS система'), + (27,7,'delete_pmsconfiguration','Can delete PMS система'), + (28,7,'view_pmsconfiguration','Can view PMS система'), + (29,8,'add_pmsintegrationlog','Can add Журнал интеграции PMS'), + (30,8,'change_pmsintegrationlog','Can change Журнал интеграции PMS'), + (31,8,'delete_pmsintegrationlog','Can delete Журнал интеграции PMS'), + (32,8,'view_pmsintegrationlog','Can view Журнал интеграции PMS'), + (33,9,'add_apiconfiguration','Can add Конфигурация API'), + (34,9,'change_apiconfiguration','Can change Конфигурация API'), + (35,9,'delete_apiconfiguration','Can delete Конфигурация API'), + (36,9,'view_apiconfiguration','Can view Конфигурация API'), + (37,10,'add_fraudlog','Can add Журнал мошенничества'), + (38,10,'change_fraudlog','Can change Журнал мошенничества'), + (39,10,'delete_fraudlog','Can delete Журнал мошенничества'), + (40,10,'view_fraudlog','Can view Журнал мошенничества'), + (41,11,'add_guest','Can add Гость'), + (42,11,'change_guest','Can change Гость'), + (43,11,'delete_guest','Can delete Гость'), + (44,11,'view_guest','Can view Гость'), + (45,12,'add_reservation','Can add Бронирование'), + (46,12,'change_reservation','Can change Бронирование'), + (47,12,'delete_reservation','Can delete Бронирование'), + (48,12,'view_reservation','Can view Бронирование'), + (49,13,'add_userhotel','Can add Пользователь отеля'), + (50,13,'change_userhotel','Can change Пользователь отеля'), + (51,13,'delete_userhotel','Can delete Пользователь отеля'), + (52,13,'view_userhotel','Can view Пользователь отеля'), + (53,14,'add_apirequestlog','Can add Журнал запросов API'), + (54,14,'change_apirequestlog','Can change Журнал запросов API'), + (55,14,'delete_apirequestlog','Can delete Журнал запросов API'), + (56,14,'view_apirequestlog','Can view Журнал запросов API'), + (57,15,'add_hotel','Can add Отель'), + (58,15,'change_hotel','Can change Отель'), + (59,15,'delete_hotel','Can delete Отель'), + (60,15,'view_hotel','Can view Отель'), + (61,16,'add_localuseractivitylog','Can add local user activity log'), + (62,16,'change_localuseractivitylog','Can change local user activity log'), + (63,16,'delete_localuseractivitylog','Can delete local user activity log'), + (64,16,'view_localuseractivitylog','Can view local user activity log'), + (65,17,'add_useractivitylog','Can add Журнал активности'), + (66,17,'change_useractivitylog','Can change Журнал активности'), + (67,17,'delete_useractivitylog','Can delete Журнал активности'), + (68,17,'view_useractivitylog','Can view Журнал активности'), + (69,18,'add_user','Can add Пользователь'), + (70,18,'change_user','Can change Пользователь'), + (71,18,'delete_user','Can delete Пользователь'), + (72,18,'view_user','Can view Пользователь'), + (73,19,'add_notificationsettings','Can add Способ оповещения'), + (74,19,'change_notificationsettings','Can change Способ оповещения'), + (75,19,'delete_notificationsettings','Can delete Способ оповещения'), + (76,19,'view_notificationsettings','Can view Способ оповещения'), + (77,20,'add_userconfirmation','Can add Подтверждение пользователя'), + (78,20,'change_userconfirmation','Can change Подтверждение пользователя'), + (79,20,'delete_userconfirmation','Can delete Подтверждение пользователя'), + (80,20,'view_userconfirmation','Can view Подтверждение пользователя'), + (81,21,'add_scheduledtask','Can add Задача'), + (82,21,'change_scheduledtask','Can change Задача'), + (83,21,'delete_scheduledtask','Can delete Задача'), + (84,21,'view_scheduledtask','Can view Задача'); +INSERT INTO "auth_user" ("id","password","last_login","is_superuser","username","last_name","email","is_staff","is_active","date_joined","first_name") VALUES (1,'pbkdf2_sha256$870000$0tWRKvUavKHjKmwWjWsfYc$mfqBdzr5TB74K1f9OHCI3w/66VZE7vY53MEpgUT73/4=','2024-12-10 06:59:08.529292',1,'trevor1985','','shadow85@list.ru',1,1,'2024-12-09 09:30:44.551380',''); +INSERT INTO "users_user" ("id","password","last_login","is_superuser","username","first_name","last_name","email","is_staff","is_active","date_joined","telegram_id","chat_id","role","confirmed") VALUES (1,'Andrey K. Tsoy','2024-12-09 09:34:41',1,'andrew','','','',0,1,'2024-12-09 09:34:27',556399210,556399210,'hotel_user',1); +INSERT INTO "users_notificationsettings" ("id","telegram_enabled","email_enabled","email","notification_time","user_id") VALUES (1,0,1,'a.choi@smartsoltech.kr','09:00:00',1); +INSERT INTO "hotels_hotel" ("id","name","created_at","api_id","pms_id") VALUES (1,'Golden Hills 3','2024-12-09 09:34:11.463934',NULL,1), + (2,'Golden Hills 4','2024-12-09 09:34:21.782571',NULL,2), + (3,'Как дома','2024-12-09 10:19:47.095457',NULL,3), + (4,'Test','2024-12-10 04:25:37.534927',NULL,4); +INSERT INTO "pms_integration_pmsintegrationlog" ("id","checked_at","status","message","hotel_id") VALUES (1,'2024-12-09 09:36:43.044421','error','Плагин для PMS Shelter PMS не найден.',2), + (2,'2024-12-09 09:38:09.595349','error','Плагин для PMS Shelter PMS не найден.',1), + (3,'2024-12-09 09:40:38.721678','error','Плагин для PMS Shelter PMS не найден.',2), + (4,'2024-12-09 10:07:44.968856','error','Плагин для PMS Shelter PMS не найден.',1); +INSERT INTO "hotels_userhotel" ("id","hotel_id","user_id") VALUES (1,1,1), + (2,2,1), + (3,3,1), + (4,4,1); +INSERT INTO "django_session" ("session_key","session_data","expire_date") VALUES ('wjcybubh02eoyth1e4pm9cgjc8c9rk1v','.eJxVjEEOwiAQRe_C2hBgpgVcuvcMZIBBqoYmpV0Z765NutDtf-_9lwi0rTVsnZcwZXEWWpx-t0jpwW0H-U7tNss0t3WZotwVedAur3Pm5-Vw_w4q9fqthxwtITBY9MlGtJ6ADZiiAdyQ7KhVyVgcGY7gEL1BVqMhH51iNl68P9F6N0w:1tKa6w:zJT5PgcESbBBG5gKuJsyfV6EOxizdevxDzI4QrGbLsc','2024-12-23 09:31:26.682056'), + ('zhfmhutrq2o277smded60dpbxurhyqrv','.eJxVjEEOwiAQRe_C2hBgpgVcuvcMZIBBqoYmpV0Z765NutDtf-_9lwi0rTVsnZcwZXEWWpx-t0jpwW0H-U7tNss0t3WZotwVedAur3Pm5-Vw_w4q9fqthxwtITBY9MlGtJ6ADZiiAdyQ7KhVyVgcGY7gEL1BVqMhH51iNl68P9F6N0w:1tKpiq:sIJzaN8dmZGRQlRUklQ9SNyhbyMuSLYMj-62RXTX5no','2024-12-24 02:11:36.624898'), + ('mz2w18vdzao572ss2ikr5tgj0w2rrbff','.eJxVjEEOwiAQRe_C2hBgpgVcuvcMZIBBqoYmpV0Z765NutDtf-_9lwi0rTVsnZcwZXEWWpx-t0jpwW0H-U7tNss0t3WZotwVedAur3Pm5-Vw_w4q9fqthxwtITBY9MlGtJ6ADZiiAdyQ7KhVyVgcGY7gEL1BVqMhH51iNl68P9F6N0w:1tKuD6:CESE5WIEOm7OzSRbIws0uyat8L3VjPyjOBTBeK_YzFY','2024-12-24 06:59:08.533787'); +INSERT INTO "pms_integration_pmsconfiguration" ("id","name","url","token","username","password","created_at","plugin_name","private_key","public_key") VALUES (1,'Shelter Golden Hills 3','https://pms.frontdesk24.ru/sheltercloudapi/Reservations/ByFilter','A0DD4B7F-0381-4096-B46E-D22F1A571996',NULL,NULL,'2024-12-09 09:32:31.348604','Shelter',NULL,NULL), + (2,'Shelter Golden Hills 4','https://pms.frontdesk24.ru/sheltercloudapi/Reservations/ByFilter','A0DD4B7F-0381-4096-B46E-D22F1A571996',NULL,NULL,'2024-12-09 09:33:10.514492','shelter',NULL,NULL), + (3,'Как дома / RealtyCalendar','https://realtycalendar.ru/api/v1/bookings/',NULL,NULL,NULL,'2024-12-10 03:07:04.218499','realtycalendar','a3669a349b9911cf774ec30ba9523582','b95e293cf07c84dfce44ec41bdced96a'), + (4,'Bnovo','https://online.bnovo.ru',NULL,'16798','a46da27476f02d1f','2024-12-10 04:18:40.567212','bnovo',NULL,NULL); +INSERT INTO "scheduler_scheduledtask" ("id","last_run","days","hours","minutes","months","active","task_name","function_path","weekdays") VALUES (1,NULL,'*','*','*','*',1,'bot running','bot.utils.bot_setup.setup_bot','[6]'), + (2,NULL,'*','*','*','*',1,'bot.check_pms','manage.main','2'); +COMMIT; diff --git a/hotels/admin.py b/hotels/admin.py index 300dfbb6..8348a062 100644 --- a/hotels/admin.py +++ b/hotels/admin.py @@ -4,9 +4,7 @@ from .models import ( Hotel, UserHotel, APIConfiguration, - APIRequestLog, Reservation, - Guest, FraudLog ) from django.urls import path @@ -69,22 +67,6 @@ class UserHotelAdmin(admin.ModelAdmin): # ordering = ('-hotel',) -@admin.register(APIConfiguration) -class ApiConfigurationAdmin(admin.ModelAdmin): - list_display = ('name', 'url', 'token', 'username', 'password', 'last_updated') - search_fields = ('name', 'url', 'token', 'username') - list_filter = ('last_updated',) - ordering = ('-last_updated',) - - -@admin.register(APIRequestLog) -class ApiRequestLogAdmin(admin.ModelAdmin): - list_display = ('api', 'request_time', 'response_status', 'response_data') - search_fields = ('api__name', 'request_time', 'response_status') - list_filter = ('api', 'response_status', 'request_time') - ordering = ('-request_time',) - - @admin.register(Reservation) class ReservationAdmin(admin.ModelAdmin): list_display = ('reservation_id', 'hotel', 'room_number', 'room_type', 'check_in', 'check_out', 'status', 'price', 'discount') @@ -93,9 +75,3 @@ class ReservationAdmin(admin.ModelAdmin): ordering = ('-check_in',) -@admin.register(Guest) -class GuestAdmin(admin.ModelAdmin): - list_display = ('reservation', 'name', 'birthdate', 'phone', 'email') - search_fields = ('reservation__reservation_id', 'name', 'phone', 'email') - list_filter = ('reservation',) - ordering = ('-reservation',) diff --git a/hotels/booking_analyzer.py b/hotels/booking_analyzer.py deleted file mode 100644 index c7cc64da..00000000 --- a/hotels/booking_analyzer.py +++ /dev/null @@ -1,33 +0,0 @@ -import json -import pandas as pd - -# Load the JSON file -file_path = '../../modules/analyzed_9.json' -with open(file_path, 'r', encoding='utf-8') as file: - data = json.load(file) - -# Process the data into a structured format -reservations = [] -for booking in data: - for guest in booking.get("guests", []): - reservations.append({ - "Reservation ID": booking.get("id"), - "Hotel Name": booking.get("hotelName"), - "Room Number": booking.get("roomNumber"), - "Room Type": booking.get("roomTypeName"), - "Check-in": booking.get("from"), - "Check-out": booking.get("until"), - "Status": booking.get("checkInStatus"), - "Price": booking.get("reservationPrice"), - "Discount": booking.get("discount"), - "Guest Name": f"{guest.get('lastName', '')} {guest.get('firstName', '')} {guest.get('middleName', '')}".strip(), - "Guest Birthdate": guest.get("birthDate"), - "Guest Phone": guest.get("phone"), - "Guest Email": guest.get("email"), - }) - -# Convert to DataFrame for better visualization -df_reservations = pd.DataFrame(reservations) - -# Display the structured data -import ace_tools as tools; tools.display_dataframe_to_user(name="Structured Reservations Data", dataframe=df_reservations) diff --git a/manage.py b/manage.py index 850eae6b..915feb02 100755 --- a/manage.py +++ b/manage.py @@ -2,6 +2,17 @@ """Django's command-line utility for administrative tasks.""" import os import sys +import logging + +# Настройка логирования +logging.basicConfig( + level=logging.INFO, # Уровень логирования (можно DEBUG для полной информации) + format="%(asctime)s - %(levelname)s - %(message)s", + handlers=[ + logging.FileHandler("bot.log"), # Логи будут записываться в файл bot.log + logging.StreamHandler() # Логи также будут отображаться в консоли + ] +) def main(): diff --git a/pms_integration/admin.py b/pms_integration/admin.py index ccbcf45a..ff70a6b5 100644 --- a/pms_integration/admin.py +++ b/pms_integration/admin.py @@ -8,53 +8,34 @@ from django.shortcuts import render from django import forms from pms_integration.models import PMSConfiguration, PMSIntegrationLog + class PMSConfigurationForm(forms.ModelForm): class Meta: model = PMSConfiguration - fields = "__all__" + fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + # Загружаем доступные плагины plugins = PluginLoader.load_plugins() - self.fields['plugin_name'].choices = [(plugin, plugin) for plugin in plugins.keys()] + plugin_choices = [(plugin_name, plugin_name) for plugin_name in plugins.keys()] + self.fields['plugin_name'] = forms.ChoiceField(choices=plugin_choices, required=False) @admin.register(PMSConfiguration) class PMSConfigurationAdmin(admin.ModelAdmin): form = PMSConfigurationForm - list_display = ('name', 'plugin_name', 'created_at', 'check_plugins_button') - search_fields = ('name', 'description') - list_filter = ('created_at',) + list_display = ('name', 'plugin_name', 'created_at') + search_fields = ('name', 'plugin_name') ordering = ('-created_at',) - def get_urls(self): - """Добавляем URL для проверки плагинов.""" - urls = super().get_urls() - custom_urls = [ - path("check-plugins/", self.check_plugins, name="check-plugins"), - ] - return custom_urls + urls - - def check_plugins(self, request): - """Проверка и отображение плагинов.""" + def save_model(self, request, obj, form, change): + # Проверка на наличие плагина plugins = PluginLoader.load_plugins() - plugin_details = [ - {"name": plugin_name, "doc": plugins[plugin_name].__doc__ or "Нет документации"} - for plugin_name in plugins - ] - context = { - "title": "Проверка плагинов", - "plugin_details": plugin_details, - } - return render(request, "admin/check_plugins.html", context) - - def check_plugins_button(self, obj): - """Добавляем кнопку для проверки плагинов.""" - return format_html( - 'Проверить плагины', - "/admin/pms_integration/pmsconfiguration/check-plugins/", - ) - check_plugins_button.short_description = "Проверить плагины" - + if obj.plugin_name and obj.plugin_name not in plugins.keys(): + raise ValueError(f"Выберите корректный плагин. '{obj.plugin_name}' нет среди допустимых значений.") + super().save_model(request, obj, form, change) + + @admin.register(PMSIntegrationLog) class PMSIntegrationLogAdmin(admin.ModelAdmin): list_display = ('hotel', 'checked_at', 'status', 'message') diff --git a/pms_integration/forms.py b/pms_integration/forms.py index 6d052da7..e69de29b 100644 --- a/pms_integration/forms.py +++ b/pms_integration/forms.py @@ -1,13 +0,0 @@ -from django import forms -from .models import PMSConfiguration -from .manager import PluginLoader - -class PMSConfigurationForm(forms.ModelForm): - class Meta: - model = PMSConfiguration - fields = "__all__" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - plugins = PluginLoader.load_plugins() - self.fields['plugin_name'].choices = [(plugin, plugin) for plugin in plugins.keys()] \ No newline at end of file diff --git a/pms_integration/manager.py b/pms_integration/manager.py index c37813b3..6c2823a0 100644 --- a/pms_integration/manager.py +++ b/pms_integration/manager.py @@ -7,27 +7,31 @@ from asgiref.sync import sync_to_async class PluginLoader: PLUGIN_PATH = Path(__file__).parent / "plugins" - print("Путь к папке плагинов:", PLUGIN_PATH.resolve()) - print("Содержимое папки:", list(PLUGIN_PATH.iterdir())) + @staticmethod def load_plugins(): plugins = {} + if not PluginLoader.PLUGIN_PATH.exists(): + print("Папка с плагинами не существует:", PluginLoader.PLUGIN_PATH) + return plugins + + print("Загрузка плагинов:") for file in os.listdir(PluginLoader.PLUGIN_PATH): if file.endswith("_pms.py") and not file.startswith("__"): + print(f" Plugin {file}") module_name = f"pms_integration.plugins.{file[:-3]}" try: module = importlib.import_module(module_name) for attr in dir(module): cls = getattr(module, attr) if isinstance(cls, type) and issubclass(cls, BasePMSPlugin) and cls is not BasePMSPlugin: - plugins[cls.__name__] = cls - print(f"Загружен плагин: {cls.__name__}") + plugin_name = file[:-7] # Убираем `_pms` из имени файла + print(f" Загружен плагин {plugin_name}: {cls.__name__}") + plugins[plugin_name] = cls except Exception as e: - print(f"Ошибка при загрузке модуля {module_name}: {e}") - print(f"Итоговый список плагинов: {list(plugins.keys())}") + print(f" Ошибка загрузки плагина {module_name}: {e}") return plugins - class PMSIntegrationManager: def __init__(self, hotel_id): self.hotel_id = hotel_id diff --git a/pms_integration/migrations/0002_alter_pmsconfiguration_plugin_name.py b/pms_integration/migrations/0002_alter_pmsconfiguration_plugin_name.py new file mode 100644 index 00000000..014b8df1 --- /dev/null +++ b/pms_integration/migrations/0002_alter_pmsconfiguration_plugin_name.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2024-12-10 02:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pms_integration', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='pmsconfiguration', + name='plugin_name', + field=models.CharField(blank=True, choices=[], max_length=255, null=True), + ), + ] diff --git a/pms_integration/migrations/0003_alter_pmsconfiguration_plugin_name.py b/pms_integration/migrations/0003_alter_pmsconfiguration_plugin_name.py new file mode 100644 index 00000000..9335ab81 --- /dev/null +++ b/pms_integration/migrations/0003_alter_pmsconfiguration_plugin_name.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2024-12-10 02:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pms_integration', '0002_alter_pmsconfiguration_plugin_name'), + ] + + operations = [ + migrations.AlterField( + model_name='pmsconfiguration', + name='plugin_name', + field=models.CharField(blank=True, choices=[], max_length=255, null=True, verbose_name='Плагин'), + ), + ] diff --git a/pms_integration/migrations/0004_alter_pmsconfiguration_plugin_name.py b/pms_integration/migrations/0004_alter_pmsconfiguration_plugin_name.py new file mode 100644 index 00000000..eb3629b8 --- /dev/null +++ b/pms_integration/migrations/0004_alter_pmsconfiguration_plugin_name.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2024-12-10 03:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pms_integration', '0003_alter_pmsconfiguration_plugin_name'), + ] + + operations = [ + migrations.AlterField( + model_name='pmsconfiguration', + name='plugin_name', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Плагин'), + ), + ] diff --git a/pms_integration/migrations/0005_pmsconfiguration_private_key_and_more.py b/pms_integration/migrations/0005_pmsconfiguration_private_key_and_more.py new file mode 100644 index 00000000..dd9fe759 --- /dev/null +++ b/pms_integration/migrations/0005_pmsconfiguration_private_key_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.4 on 2024-12-10 03:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pms_integration', '0004_alter_pmsconfiguration_plugin_name'), + ] + + operations = [ + migrations.AddField( + model_name='pmsconfiguration', + name='private_key', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Приватный ключ'), + ), + migrations.AddField( + model_name='pmsconfiguration', + name='public_key', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Публичный ключ'), + ), + ] diff --git a/pms_integration/models.py b/pms_integration/models.py index 5083d409..63636bb0 100644 --- a/pms_integration/models.py +++ b/pms_integration/models.py @@ -8,10 +8,12 @@ class PMSConfiguration(models.Model): name = models.CharField(max_length=255, verbose_name="Название PMS") url = models.URLField(verbose_name="URL API") token = models.CharField(max_length=255, blank=True, null=True, verbose_name="Токен") + public_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="Публичный ключ") + private_key = models.CharField(max_length=255, blank=True, null=True, verbose_name="Приватный ключ") username = models.CharField(max_length=255, blank=True, null=True, verbose_name="Логин") password = models.CharField(max_length=255, blank=True, null=True, verbose_name="Пароль") - plugin_name = models.CharField(max_length=255, verbose_name="Название плагина") - created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") # Добавлено поле + plugin_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="Плагин") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания") def __str__(self): diff --git a/pms_integration/plugins/bnovo_pms.py b/pms_integration/plugins/bnovo_pms.py index 89374bee..1d3f32e2 100644 --- a/pms_integration/plugins/bnovo_pms.py +++ b/pms_integration/plugins/bnovo_pms.py @@ -1,71 +1,136 @@ import requests +import json +from datetime import datetime, timedelta from .base_plugin import BasePMSPlugin +from asgiref.sync import sync_to_async +from pms_integration.models import PMSConfiguration # Убедитесь, что модель существует -class BnovoPMS(BasePMSPlugin): - """ - Плагин для интеграции с Bnovo. - """ - json_schema = { - "type": "object", - "properties": { - "id": {"type": "integer"}, - "number": {"type": "integer"}, - "roomTypeName": {"type": "string"}, - "checkInStatus": {"type": "string"}, - "guests": {"type": "array"}, - }, - "required": ["id", "number", "roomTypeName", "checkInStatus", "guests"] - } +class BnovoPMSPlugin(BasePMSPlugin): + """Плагин для работы с PMS Bnovo.""" + + def __init__(self, config): + super().__init__(config) + self.api_url = config.url.rstrip("/") # Убираем лишний `/` в конце URL + self.username = config.username + self.password = config.password + self.token = None # SID + + if not self.api_url: + raise ValueError("Не указан URL для работы плагина.") + if not self.username or not self.password: + raise ValueError("Не указаны логин или пароль для авторизации.") + def get_default_parser_settings(self): - """ - Возвращает настройки парсера по умолчанию. - """ + """Возвращает настройки по умолчанию для обработки данных.""" return { - "field_mapping": { - "room_name": "roomNumber", - "check_in": "from", - "check_out": "until", - }, - "date_format": "%Y-%m-%dT%H:%M:%S" + "date_format": "%Y-%m-%dT%H:%M:%S", + "timezone": "UTC" } - def fetch_data(self): - response = requests.get(self.pms_config.url, headers={"Authorization": f"Bearer {self.pms_config.token}"}) - response.raise_for_status() - data = response.json() - # Проверка структуры - expected_fields = self.pms_config.parser_settings.get("fields_mapping", {}) - for field in expected_fields.values(): - if field not in data[0]: # Проверяем первую запись - raise ValueError(f"Поле {field} отсутствует в ответе API.") + async def _save_token_to_db(self, sid): + """Сохраняет токен (SID) в базу данных.""" + try: + await sync_to_async(PMSConfiguration.objects.update_or_create)( + plugin_name="bnovo", + defaults={"token": sid} + ) + print(f"[DEBUG] Токен сохранен в БД: {sid}") + except Exception as e: + print(f"[ERROR] Ошибка сохранения токена в БД: {e}") - return data - - def fetch_and_parse(self): - response = requests.get( - self.pms_config.url, - headers={"Authorization": f"Bearer {self.pms_config.token}"} - ) - self.validate_response(response) # Проверка соответствия структуры - if response.status_code != 200: - raise ValueError(f"Ошибка запроса к PMS Bnovo: {response.text}") + def _get_auth_headers(self): + """Создает заголовки авторизации.""" + headers = { + "Content-Type": "application/json", + "Accept": "application/json", + } + if self.token: + headers["Cookie"] = f"SID={self.token}" + return headers - data = response.json() - parsed_data = self.parse_data(data) - return parsed_data + async def _fetch_session(self): + """Получает идентификатор сессии (SID) через запрос.""" + url = f"{self.api_url}/" + payload = { + "username": self.username, + "password": self.password, + } - def parse_data(self, data): - # Пример разбора данных на основе JSON-маски - reservations = [] - for item in data["reservations"]: - reservation = { - "id": item["id"], - "room_number": item["roomNumber"], - "check_in": item["checkIn"], - "check_out": item["checkOut"], - "status": item["status"], - } - reservations.append(reservation) - return reservations - + print(f"[DEBUG] URL авторизации: {url}") + print(f"[DEBUG] Тело запроса: {json.dumps(payload, indent=2)}") + headers = self._get_auth_headers() + + session = requests.Session() + response = session.post(url, json=payload, headers=headers, allow_redirects=False) + + print(f"[DEBUG] Статус ответа: {response.status_code}") + print(f"[DEBUG] Ответ заголовков: {response.headers}") + print(f"[DEBUG] Cookies: {session.cookies}") + + if response.status_code == 302 and "SID" in session.cookies: + sid = session.cookies.get("SID") + self.token = sid + print(f"[DEBUG] Получен SID: {sid}") + + # Правильное сохранение в БД через sync_to_async + try: + await self._save_token_to_db(sid) + print(f"[DEBUG] Токен сохранен в БД") + except Exception as e: + print(f"[ERROR] Ошибка сохранения токена в БД: {e}") + else: + raise ValueError(f"Не удалось получить SID из ответа: {response.text}") + + async def _fetch_data(self): + """Получает данные о бронированиях с помощью эндпоинта `/dashboard`.""" + await self._fetch_session() # Авторизуемся перед каждым запросом + + now = datetime.now() + create_from = (now - timedelta(days=90)).strftime("%d.%m.%Y") # Диапазон: последние 90 дней + create_to = now.strftime("%d.%m.%Y") + + params = { + "create_from": create_from, + "create_to": create_to, + "status_ids": "1", + "advanced_search": 2, # Обязательный параметр + "c": 100, # Количество элементов на странице (максимум 100) + "page": 1, # Начальная страница + "order_by": "create_date.asc", # Сортировка по возрастанию даты создания + } + + headers = self._get_auth_headers() + + all_bookings = [] # Для сохранения всех бронирований + while True: + print(f"[DEBUG] Запрос к /dashboard с параметрами: {json.dumps(params, indent=2)}") + response = requests.get(f"{self.api_url}/dashboard", headers=headers, params=params) + + print(f"[DEBUG] Статус ответа: {response.status_code}") + if response.status_code != 200: + raise ValueError(f"Ошибка при получении данных: {response.status_code}, {response.text}") + + data = response.json() + print(json.dumps(data, indent=2)) + bookings = data.get("bookings", []) + all_bookings.extend(bookings) + + print(f"[DEBUG] Получено бронирований: {len(bookings)}") + print(f"[DEBUG] Всего бронирований: {len(all_bookings)}") + + # Проверка на наличие следующей страницы + pages_info = data.get("pages", {}) + current_page = pages_info.get("current_page", 1) + total_pages = pages_info.get("total_pages", 1) + + if current_page >= total_pages: + break # Все страницы загружены + + params["page"] += 1 # Переход на следующую страницу + + if not all_bookings: + print("[DEBUG] Нет бронирований за указанный период.") + else: + print(f"[DEBUG] Полученные бронирования: {json.dumps(all_bookings, indent=2)}") + return all_bookings \ No newline at end of file diff --git a/pms_integration/plugins/realtycalendar_pms.py b/pms_integration/plugins/realtycalendar_pms.py index f7316142..f9176617 100644 --- a/pms_integration/plugins/realtycalendar_pms.py +++ b/pms_integration/plugins/realtycalendar_pms.py @@ -1,78 +1,254 @@ -import hashlib -import requests -import json -from datetime import datetime -from hotels.models import Reservation +# import requests +# import hashlib +# import json +# from .base_plugin import BasePMSPlugin +# from datetime import datetime, timedelta +# from asgiref.sync import sync_to_async -from pms_integration.plugins.base_plugin import BasePMSPlugin + +# class RealtyCalendarPlugin(BasePMSPlugin): +# """Плагин для импорта данных из системы RealtyCalendar +# """ +# def __init__(self, config): +# super().__init__(config) +# self.public_key = config.public_key +# self.private_key = config.private_key +# self.api_url = config.url.rstrip("/") # Убираем лишний `/` в конце URL + +# if not self.public_key or not self.private_key: +# raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar") +# def get_default_parser_settings(self): +# """ +# Возвращает настройки по умолчанию для обработки данных. +# """ +# return { +# "date_format": "%Y-%m-%dT%H:%M:%S", +# "timezone": "UTC" +# } +# def _get_sorted_keys(self, obj): +# """ +# Возвращает отсортированный по имени список ключей. +# """ +# return sorted(list(obj.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) +# return string + self.private_key + +# def _generate_md5(self, string): +# """ +# Генерирует MD5-хеш от строки. +# """ +# return hashlib.md5(string.encode("utf-8")).hexdigest() + +# def _generate_sign(self, data): +# """ +# Генерирует подпись для данных запроса. +# """ +# data_string = self._generate_data_string(data) +# return self._generate_md5(data_string) + +# def fetch_data(self): +# """ +# Выполняет запрос к API RealtyCalendar для получения данных о бронированиях. +# """ +# base_url = f"https://realtycalendar.ru/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) + +# # Отправляем запрос +# print(f"URL запроса: {base_url}") +# print(f"Заголовки: {headers}") +# print(f"Данные запроса: {data}") + +# response = requests.post(url=base_url, headers=headers, json=data) + +# # Логируем результат +# print(f"Статус ответа: {response.status_code}") +# print(f"Ответ: {response.text}") + +# # Проверяем успешность запроса +# if response.status_code == 200: +# return response.json().get("bookings", []) +# else: +# raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}, {response.text}") + +# async def _save_to_db(self, data, hotel_id): +# """ +# Сохраняет данные о бронированиях в базу данных. +# """ +# from hotels.models import Reservation, Hotel + +# hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id) +# for item in data: +# try: +# reservation, created = await sync_to_async(Reservation.objects.update_or_create)( +# reservation_id=item["id"], +# hotel=hotel, +# defaults={ +# "room_number": item.get("apartment_id", ""), # ID квартиры +# "check_in": datetime.strptime(item["begin_date"], "%Y-%m-%d"), # Дата заезда +# "check_out": datetime.strptime(item["end_date"], "%Y-%m-%d"), # Дата выезда +# "status": item.get("status", ""), # Статус бронирования +# "price": item.get("amount", 0), # Сумма оплаты +# "client_name": item["client"].get("fio", ""), # Имя клиента +# "client_email": item["client"].get("email", ""), # Email клиента +# "client_phone": item["client"].get("phone", ""), # Телефон клиента +# } +# ) +# print(f"{'Создана' if created else 'Обновлена'} запись: {reservation}") +# except Exception as e: +# print(f"Ошибка при сохранении бронирования ID {item['id']}: {e}") + + +import requests +import hashlib +import json +from .base_plugin import BasePMSPlugin +from datetime import datetime, timedelta +from asgiref.sync import sync_to_async class RealtyCalendarPlugin(BasePMSPlugin): - """ - Плагин для взаимодействия с RealtyCalendar. + """Плагин для импорта данных из системы RealtyCalendar """ def __init__(self, config): super().__init__(config) - self.public_key = config.token # Используем `token` как публичный ключ - self.private_key = config.password # Используем `password` как приватный ключ - self.base_url = config.url + self.public_key = config.public_key + self.private_key = config.private_key + self.api_url = config.url.rstrip("/") - def generate_sign(self, params): - """ - Генерация подписи запроса. - :param params: Параметры запроса. - :return: Подпись. - """ - sorted_keys = sorted(params.keys()) - data_string = ''.join(f"{key}={params[key]}" for key in sorted_keys) - sign_string = f"{data_string}{self.private_key}" - return hashlib.md5(sign_string.encode('utf-8')).hexdigest() + if not self.public_key or not self.private_key: + raise ValueError("Публичный или приватный ключ отсутствует для RealtyCalendar") - def fetch_data(self, start_date=None, end_date=None): + def get_default_parser_settings(self): """ - Получение данных из RealtyCalendar. - :param start_date: Начальная дата (формат YYYY-MM-DD). - :param end_date: Конечная дата (формат YYYY-MM-DD). - :return: Список данных бронирования. + Возвращает настройки по умолчанию для обработки данных. """ - if not start_date: - start_date = datetime.now().strftime('%Y-%m-%d') - if not end_date: - end_date = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d') - - params = { - 'begin_date': start_date, - 'end_date': end_date, + return { + "date_format": "%Y-%m-%dT%H:%M:%S", + "timezone": "UTC" } - params['sign'] = self.generate_sign(params) - url = f"{self.base_url}/bookings/{self.public_key}/" + def _get_sorted_keys(self, obj): + """ + Возвращает отсортированный по имени список ключей. + """ + sorted_keys = sorted(list(obj.keys())) + print(f"[DEBUG] Отсортированные ключи: {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) + print(f"[DEBUG] Сформированная строка данных: {string}") + return string + self.private_key + + def _generate_md5(self, string): + """ + Генерирует MD5-хеш от строки. + """ + md5_hash = hashlib.md5(string.encode("utf-8")).hexdigest() + print(f"[DEBUG] Сформированный MD5-хеш: {md5_hash}") + return md5_hash + + def _generate_sign(self, data): + """ + Генерирует подпись для данных запроса. + """ + data_string = self._generate_data_string(data) + print(f"[DEBUG] Строка для подписи: {data_string}") + sign = self._generate_md5(data_string) + print(f"[DEBUG] Подпись: {sign}") + return sign + + def _fetch_data(self): + """ + Выполняет запрос к API RealtyCalendar для получения данных о бронированиях. + """ + base_url = f"https://realtycalendar.ru/api/v1/bookings/{self.public_key}/" headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', + "Accept": "application/json", + "Content-Type": "application/json", } - response = requests.post(url, json=params, headers=headers) - response.raise_for_status() + # Определяем даты выборки + now = datetime.now() + data = { + "begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"), + "end_date": now.strftime("%Y-%m-%d"), + } - data = response.json() - return data.get('bookings', []) + print(f"[DEBUG] Даты выборки: {data}") - @staticmethod - def save_data(bookings): + # Генерация подписи + data["sign"] = self._generate_sign(data) + + # Отправляем запрос + print(f"[DEBUG] URL запроса: {base_url}") + print(f"[DEBUG] Заголовки: {headers}") + print(f"[DEBUG] Данные запроса: {data}") + + response = requests.post(url=base_url, headers=headers, json=data) + + # Логируем результат + print(f"[DEBUG] Статус ответа: {response.status_code}") + print(f"[DEBUG] Ответ: {response.text}") + + # Проверяем успешность запроса + if response.status_code == 200: + bookings = response.json().get("bookings", []) + print(f"[DEBUG] Полученные данные бронирований: {bookings}") + return bookings + else: + raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}, {response.text}") + + + async def _save_to_db(self, data, hotel_id): """ - Сохранение данных бронирования в базу данных. - :param bookings: Список бронирований. + Сохраняет данные о бронированиях в базу данных. """ - for booking in bookings: - Reservation.objects.update_or_create( - external_id=booking['id'], - defaults={ - 'check_in': booking['begin_date'], - 'check_out': booking['end_date'], - 'amount': booking['amount'], - 'notes': booking.get('notes', ''), - 'guest_name': booking['client']['fio'], - 'guest_phone': booking['client']['phone'], - }, - ) + from hotels.models import Reservation, Hotel + + hotel = await sync_to_async(Hotel.objects.get)(id=hotel_id) + print(f"[DEBUG] Загружен отель: {hotel.name}") + + for item in data: + print(f"[DEBUG] Обработка бронирования: {item}") + try: + reservation, created = await sync_to_async(Reservation.objects.update_or_create)( + reservation_id=item["id"], + hotel=hotel, + defaults={ + "room_number": item.get("apartment_id", ""), # ID квартиры + "check_in": datetime.strptime(item["begin_date"], "%Y-%m-%d"), # Дата заезда + "check_out": datetime.strptime(item["end_date"], "%Y-%m-%d"), # Дата выезда + "status": item.get("status", ""), # Статус бронирования + "price": item.get("amount", 0), # Сумма оплаты + "client_name": item["client"].get("fio", ""), # Имя клиента + "client_email": item["client"].get("email", ""), # Email клиента + "client_phone": item["client"].get("phone", ""), # Телефон клиента + } + ) + print(f"[DEBUG] {'Создана' if created else 'Обновлена'} запись: {reservation}") + except Exception as e: + print(f"[DEBUG] Ошибка при сохранении бронирования ID {item['id']}: {e}") diff --git a/pms_integration/plugins/shelter_pms.py b/pms_integration/plugins/shelter_pms.py index 22aad21a..98c04aca 100644 --- a/pms_integration/plugins/shelter_pms.py +++ b/pms_integration/plugins/shelter_pms.py @@ -8,6 +8,9 @@ from hotels.models import Hotel class Shelter(BasePMSPlugin): + """ + Плагин для PMS Shelter Coud. + """ def __init__(self, config): super().__init__(config) self.token = config.token diff --git a/pms_integration/test_requests.py b/pms_integration/test_requests.py new file mode 100644 index 00000000..d15fd51c --- /dev/null +++ b/pms_integration/test_requests.py @@ -0,0 +1,73 @@ +# import requests +# import json + +# # Функция авторизации +# def authorize(username, password): +# url = "https://online.bnovo.ru/" +# headers = { +# "accept": "application/json", +# "Content-Type": "application/json" +# } +# payload = { +# "username": username, +# "password": password +# } + +# response = requests.post(url, headers=headers, json=payload, allow_redirects=False) +# print(f"[DEBUG] Статус авторизации: {response.status_code}") +# print(f"[DEBUG] Заголовки ответа: {response.headers}") + +# if response.status_code == 302 and "SID" in response.cookies: +# sid = response.cookies.get("SID") +# print(f"[DEBUG] Получен SID: {sid}") +# return sid +# else: +# raise ValueError(f"Ошибка авторизации: {response.text}") + +# # Функция получения данных с /dashboard +# def fetch_dashboard(sid, create_from, create_to, status_ids, page=1, count=10): +# url = f"https://online.bnovo.ru/dashboard" +# headers = { +# "accept": "application/json", +# "Cookie": f"SID={sid}" +# } +# params = { +# "create_from": create_from, +# "create_to": create_to, +# "advanced_search": 2, +# "status_ids": status_ids, +# "c": count, +# "page": page, +# "order_by": "create_date.asc" +# } + +# response = requests.get(url, headers=headers, params=params) +# print(f"[DEBUG] Статус запроса: {response.status_code}") +# print(f"[DEBUG] Ответ: {json.dumps(response.json(), indent=2, ensure_ascii=False)}") + +# if response.status_code == 200: +# return response.json() +# else: +# raise ValueError(f"Ошибка при запросе данных: {response.text}") + +# # Тестовый вызов +# try: +# username = "cto@hotelantifraud.ru" +# password = "tD8wC1zP9tiT6mY1" + +# # Авторизация +# sid = authorize(username, password) + +# # Получение бронирований +# bookings = fetch_dashboard( +# sid=sid, +# create_from="25.09.2024", +# create_to="05.10.2024", +# status_ids="1", +# page=1, +# count=10 +# ) +# print(f"[INFO] Полученные бронирования: {json.dumps(bookings, indent=2, ensure_ascii=False)}") + +# except Exception as e: +# print(f"[ERROR] {e}") diff --git a/requirements.txt b/requirements.txt index 037e7ec7..095d2ade 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,12 @@ ace_tools==0.0 +aiohappyeyeballs==2.4.4 +aiohttp==3.11.10 +aiosignal==1.3.1 anyio==4.6.2.post1 APScheduler==3.11.0 asgiref==3.8.1 +async-timeout==5.0.1 +attrs==24.2.0 certifi==2024.8.30 charset-normalizer==3.4.0 Django==5.1.4 @@ -9,26 +14,41 @@ django-filter==24.3 django-jazzmin==3.0.1 django-jet==1.0.8 et_xmlfile==2.0.0 +exceptiongroup==1.2.2 fpdf==1.7.2 +frozenlist==1.5.0 +geoip2==4.8.1 h11==0.14.0 httpcore==1.0.7 httpx==0.28.0 idna==3.10 +jsonschema==4.23.0 +jsonschema-specifications==2024.10.1 +maxminddb==2.6.2 +multidict==6.1.0 numpy==2.1.3 openpyxl==3.1.5 pandas==2.2.3 +pathspec==0.12.1 pillow==11.0.0 +propcache==0.2.1 PyMySQL==1.1.1 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 python-telegram-bot==21.8 pytz==2024.2 PyYAML==6.0.2 +referencing==0.35.1 requests==2.32.3 +rpds-py==0.22.3 six==1.17.0 sniffio==1.3.1 sqlparse==0.5.2 +typing_extensions==4.12.2 tzdata==2024.2 tzlocal==5.2 +ua-parser==1.0.0 +ua-parser-builtins==0.18.0.post1 urllib3==2.2.3 -jsonschema \ No newline at end of file +user-agents==2.2.0 +yarl==1.18.3 diff --git a/scheduler/__init__.py b/scheduler/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scheduler/admin.py b/scheduler/admin.py new file mode 100644 index 00000000..8bb0d59f --- /dev/null +++ b/scheduler/admin.py @@ -0,0 +1,59 @@ +from django.contrib import admin +from django import forms +from django.utils.functional import cached_property +from .models import ScheduledTask +from django.templatetags.static import static +from scheduler.utils import get_project_functions + +class CustomAdmin(admin.ModelAdmin): + class Media: + css = {"all": (static("scheduler/admin.css"),)} + js = (static("scheduler/admin.js"),) + +class ScheduledTaskForm(forms.ModelForm): + DAYS_OF_WEEK_CHOICES = [ + (0, "Воскресенье"), + (1, "Понедельник"), + (2, "Вторник"), + (3, "Среда"), + (4, "Четверг"), + (5, "Пятница"), + (6, "Суббота"), + ] + + weekdays = forms.MultipleChoiceField( + choices=DAYS_OF_WEEK_CHOICES, + widget=forms.CheckboxSelectMultiple, + label="Дни недели", + required=False, # Опционально + ) + + class Meta: + model = ScheduledTask + fields = [ + "task_name", + "function_path", + "minutes", + "hours", + "months", + "weekdays", # Используем только поле с галочками + "active", + ] + + def clean_weekdays(self): + """ + Преобразуем список выбранных дней в строку для хранения в базе. + """ + weekdays = self.cleaned_data.get("weekdays", []) + return ",".join(map(str, weekdays)) + +@admin.register(ScheduledTask) +class ScheduledTaskAdmin(admin.ModelAdmin): + form = ScheduledTaskForm + list_display = ("task_name", "function_path", "active", "formatted_last_run") + list_filter = ("active",) + search_fields = ("task_name", "function_path") + + def formatted_last_run(self, obj): + return obj.last_run.strftime("%Y-%m-%d %H:%M:%S") if obj.last_run else "Никогда" + formatted_last_run.short_description = "Последний запуск" \ No newline at end of file diff --git a/scheduler/apps.py b/scheduler/apps.py new file mode 100644 index 00000000..3a3846a7 --- /dev/null +++ b/scheduler/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SchedulerConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'scheduler' diff --git a/scheduler/migrations/0001_initial.py b/scheduler/migrations/0001_initial.py new file mode 100644 index 00000000..b67db412 --- /dev/null +++ b/scheduler/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.4 on 2024-12-10 08:38 + +import scheduler.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ScheduledTask', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('task_name', models.CharField(max_length=255, verbose_name='Название задачи')), + ('function_path', models.CharField(choices=scheduler.models.get_available_functions, max_length=500, verbose_name='Путь к функции (модуль.функция)')), + ('minutes', models.CharField(default='*', max_length=255, verbose_name='Минуты')), + ('hours', models.CharField(default='*', max_length=255, verbose_name='Часы')), + ('days', models.CharField(default='*', max_length=255, verbose_name='Дни')), + ('months', models.CharField(default='*', max_length=255, verbose_name='Месяцы')), + ('weekdays', models.JSONField(default=list, verbose_name='Дни недели')), + ('active', models.BooleanField(default=True, verbose_name='Активно')), + ('last_run', models.DateTimeField(blank=True, null=True, verbose_name='Последний запуск')), + ], + ), + ] diff --git a/scheduler/migrations/__init__.py b/scheduler/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scheduler/models.py b/scheduler/models.py new file mode 100644 index 00000000..bb49a1e7 --- /dev/null +++ b/scheduler/models.py @@ -0,0 +1,47 @@ +from django.db import models +from django.utils.timezone import now + + +class ScheduledTask(models.Model): + task_name = models.CharField(max_length=255) + function_path = models.CharField(max_length=255) + minutes = models.CharField(max_length=255) + hours = models.CharField(max_length=255) + months = models.CharField(max_length=255) + weekdays = models.CharField(max_length=100, blank=True, default="") + active = models.BooleanField(default=True) + last_run = models.DateTimeField(null=True, blank=True) + + def __str__(self): + return self.name + + def clean_weekdays(self): + """Приводим список в строку при сохранении.""" + if isinstance(self.weekdays, list): + self.weekdays = ",".join(map(str, self.weekdays)) + + class Meta: + verbose_name = "Запланированная задача" + verbose_name_plural = "Запланированные задачи" + +def get_available_functions(): + from scheduler.utils import get_project_functions + return [(path, name) for path, name in get_project_functions()] + +class ScheduledTask(models.Model): + task_name = models.CharField(max_length=255, verbose_name="Название задачи") + function_path = models.CharField( + max_length=500, + choices=get_available_functions, + verbose_name="Путь к функции (модуль.функция)", + ) + minutes = models.CharField(max_length=255, verbose_name="Минуты", default="*") + hours = models.CharField(max_length=255, verbose_name="Часы", default="*") + days = models.CharField(max_length=255, verbose_name="Дни", default="*") + months = models.CharField(max_length=255, verbose_name="Месяцы", default="*") + weekdays = models.JSONField(default=list, verbose_name="Дни недели") + active = models.BooleanField(default=True, verbose_name="Активно") + last_run = models.DateTimeField(blank=True, null=True, verbose_name="Последний запуск") + + def __str__(self): + return self.task_name \ No newline at end of file diff --git a/scheduler/static/scheduler/admin.css b/scheduler/static/scheduler/admin.css new file mode 100644 index 00000000..2a6396a1 --- /dev/null +++ b/scheduler/static/scheduler/admin.css @@ -0,0 +1,16 @@ +.checkbox-row { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.checkbox-row label { + display: inline-block; + width: auto; + margin-right: 10px; +} + +.form-row.last_run span { + font-weight: bold; + margin-left: 10px; +} diff --git a/scheduler/tasks.py b/scheduler/tasks.py new file mode 100644 index 00000000..eb93f250 --- /dev/null +++ b/scheduler/tasks.py @@ -0,0 +1,66 @@ +from apscheduler.schedulers.base import BaseScheduler +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from importlib import import_module +from scheduler.models import ScheduledTask +import importlib +from apscheduler.triggers.cron import CronTrigger + + +def format_weekdays(weekdays): + """Преобразует список дней недели в строку.""" + if isinstance(weekdays, list): + return ",".join(map(str, weekdays)) + return str(weekdays) + +def run_task(task): + """ + Выполняет задачу, указанную в модели ScheduledTask. + """ + module_name, func_name = task.module_path.rsplit(".", 1) + module = import_module(module_name) + func = getattr(module, func_name) + func() + +def setup_scheduler(): + """Настройка планировщика задач из БД.""" + print("Настройка планировщика задач...") + scheduler = AsyncIOScheduler() + + tasks = ScheduledTask.objects.filter(active=True) + for task in tasks: + scheduler.add_job( + run_task, + "cron", + id=task.name, + minute=task.cron_minute, + hour=task.cron_hour, + day=task.cron_day, + month=task.cron_month, + day_of_week=task.cron_weekday, + args=[task], + ) + scheduler.start() + print("Планировщик запущен.") + return scheduler + +def load_tasks_to_scheduler(scheduler: BaseScheduler): + tasks = ScheduledTask.objects.filter(active=True) + for task in tasks: + try: + module_name, func_name = task.function_path.rsplit('.', 1) + module = import_module(module_name) + func = getattr(module, func_name) + + scheduler.add_job( + func, + trigger="cron", + minute=task.minutes, + hour=task.hours, + day=task.days or "*", + month=task.months or "*", + day_of_week=task.weekdays or "*", + id=str(task.id), + replace_existing=True, + ) + except Exception as e: + print(f"Ошибка при добавлении задачи '{task.task_name}': {e}") \ No newline at end of file diff --git a/scheduler/templates/admin/scheduler/scheduledtasks/change_form.html b/scheduler/templates/admin/scheduler/scheduledtasks/change_form.html new file mode 100644 index 00000000..660591fa --- /dev/null +++ b/scheduler/templates/admin/scheduler/scheduledtasks/change_form.html @@ -0,0 +1,42 @@ +{% extends "admin/change_form.html" %} +{% load static %} + +{% block extrahead %} + {{ block.super }} + +{% endblock %} + +{% block content %} +
+
+ {% csrf_token %} + {{ adminform.non_field_errors }} +
+ {% for fieldset in adminform %} +
+ {{ fieldset.name }} + {% for line in fieldset %} +
+ {% if line.field.name == 'last_run' %} +
+ + {{ line.field.contents }} +
+ {% else %} + {{ line.errors }} + {{ line.field }} + {% if line.field.is_checkbox %} +
{{ line.field }}
+ {% endif %} + {% endif %} +
+ {% endfor %} +
+ {% endfor %} +
+
+ {{ submit_buttons }} +
+
+
+{% endblock %} diff --git a/scheduler/test_module.py b/scheduler/test_module.py new file mode 100644 index 00000000..09ac24cc --- /dev/null +++ b/scheduler/test_module.py @@ -0,0 +1,6 @@ +def test_function(): + """тестовая функция для проверки планировщика + + """ + print("Hello, World!") + return "Hello, World!" diff --git a/scheduler/tests.py b/scheduler/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/scheduler/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/scheduler/utils.py b/scheduler/utils.py new file mode 100644 index 00000000..55a4fab0 --- /dev/null +++ b/scheduler/utils.py @@ -0,0 +1,71 @@ +from apscheduler.schedulers.asyncio import AsyncIOScheduler +import os +import inspect +import importlib +from typing import List, Tuple +from pathspec import PathSpec + +def reload_tasks_periodically(scheduler: AsyncIOScheduler): + """Перезагрузка задач из базы данных каждые 5 минут.""" + from scheduler.tasks import load_tasks_to_scheduler + scheduler.add_job(lambda: load_tasks_to_scheduler(scheduler), "interval", minutes=5) + + + +def load_gitignore_patterns(project_root: str) -> PathSpec: + """ + Загружает паттерны из файла .gitignore. + """ + gitignore_path = os.path.join(project_root, ".gitignore") + if os.path.exists(gitignore_path): + with open(gitignore_path, "r", encoding="utf-8") as f: + patterns = f.readlines() + return PathSpec.from_lines("gitwildmatch", patterns) + return PathSpec.from_lines("gitwildmatch", []) # Пустой PathSpec + +def get_project_functions() -> List[Tuple[str, str]]: + """ + Сканирует проект и возвращает список всех функций в формате (путь, имя функции), + исключая файлы и папки, указанные в .gitignore. + """ + functions = [] + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + # Загружаем паттерны из .gitignore + gitignore_spec = load_gitignore_patterns(project_root) + + for root, dirs, files in os.walk(project_root): + # Исключаем директории, указанные в .gitignore + dirs[:] = [d for d in dirs if not gitignore_spec.match_file(os.path.relpath(os.path.join(root, d), project_root))] + + for file in files: + file_path = os.path.relpath(os.path.join(root, file), project_root) + if ( + file.endswith(".py") and + not file.startswith("__") and + not gitignore_spec.match_file(file_path) + ): + module_path = os.path.relpath(os.path.join(root, file), project_root) + module_name = module_path.replace(os.sep, ".").replace(".py", "") + + try: + module = importlib.import_module(module_name) + for name, func in inspect.getmembers(module, inspect.isfunction): + functions.append((f"{module_name}.{name}", name)) + except Exception as e: + print(f"Ошибка загрузки модуля {module_name}: {e}") + + return functions + + + +import importlib + +def execute_function(function_path): + """ + Выполняет функцию по указанному пути. + """ + module_name, func_name = function_path.rsplit(".", 1) + module = importlib.import_module(module_name) + func = getattr(module, func_name) + return func() \ No newline at end of file diff --git a/scheduler/views.py b/scheduler/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/scheduler/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/touchh/settings.py b/touchh/settings.py index bb44e1ff..33e94d28 100644 --- a/touchh/settings.py +++ b/touchh/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = [ 'pms_integration', 'hotels', 'users', + 'scheduler' ] MIDDLEWARE = [