From 85b4c809dd2a043dbfe813a4101c7547f22f3ff2 Mon Sep 17 00:00:00 2001 From: trevor Date: Thu, 12 Dec 2024 21:11:01 +0900 Subject: [PATCH] shelter plugin completed --- bot/operations/hotels.py | 70 +- pms_integration/plugins/ecvi_pms.py | 6 +- pms_integration/plugins/shelter_pms.py | 260 ++--- reports/GoldenHills 4_report.pdf | Bin 13906 -> 12782 bytes tmp_data/wpts_user_activity_log.json | 1252 ++++++++++++++++++++++++ 5 files changed, 1458 insertions(+), 130 deletions(-) create mode 100644 tmp_data/wpts_user_activity_log.json diff --git a/bot/operations/hotels.py b/bot/operations/hotels.py index 7b5b55cd..0874fc32 100644 --- a/bot/operations/hotels.py +++ b/bot/operations/hotels.py @@ -121,6 +121,52 @@ async def delete_hotel(update: Update, context): # await pms_manager.save_log("error", str(e)) # await query.edit_message_text(f"❌ Ошибка: {e}") +# async def check_pms(update, context): +# query = update.callback_query + +# try: +# # Получение ID отеля из callback_data +# hotel_id = query.data.split("_")[2] + +# # Получение конфигурации отеля и PMS +# hotel = await sync_to_async(Hotel.objects.select_related('pms').get)(id=hotel_id) +# pms_config = hotel.pms + +# if not pms_config: +# await query.edit_message_text("PMS конфигурация не найдена.") +# return + +# # Создаем экземпляр PMSIntegrationManager +# pms_manager = PMSIntegrationManager(hotel_id=hotel_id) +# await pms_manager.load_hotel() +# await sync_to_async(pms_manager.load_plugin)() + +# # Проверяем, какой способ интеграции использовать +# if hasattr(pms_manager.plugin, 'fetch_data') and callable(pms_manager.plugin.fetch_data): +# # Плагин поддерживает метод 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 = api_client.fetch_reservations() +# else: +# # Если подходящий способ не найден +# await query.edit_message_text("Подходящий способ интеграции с PMS не найден.") +# return + +# # Сохраняем данные в базу +# from bot.utils.database import save_reservations +# await sync_to_async(save_reservations)(data) + +# # Уведомляем об успешной интеграции +# await query.edit_message_text(f"Интеграция PMS {pms_config.name} завершена успешно.") +# except Exception as e: +# # Обрабатываем и логируем ошибки +# await query.edit_message_text(f"❌ Ошибка: {str(e)}") + + + async def check_pms(update, context): query = update.callback_query @@ -144,28 +190,28 @@ async def check_pms(update, context): # Проверяем, какой способ интеграции использовать if hasattr(pms_manager.plugin, 'fetch_data') and callable(pms_manager.plugin.fetch_data): # Плагин поддерживает метод 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 = api_client.fetch_reservations() + report = await pms_manager.plugin._fetch_data() else: - # Если подходящий способ не найден await query.edit_message_text("Подходящий способ интеграции с PMS не найден.") return - # Сохраняем данные в базу - from bot.utils.database import save_reservations - await sync_to_async(save_reservations)(data) + # Формируем сообщение о результатах + result_message = ( + f"Интеграция PMS завершена успешно.\n" + f"Обработано интервалов: {report['processed_intervals']}\n" + f"Обработано записей: {report['processed_items']}\n" + f"Ошибки: {len(report['errors'])}" + ) + if report["errors"]: + result_message += "\n\nСписок ошибок:\n" + "\n".join(report["errors"]) - # Уведомляем об успешной интеграции - await query.edit_message_text(f"Интеграция PMS {pms_config.name} завершена успешно.") + await query.edit_message_text(result_message) except Exception as e: # Обрабатываем и логируем ошибки await query.edit_message_text(f"❌ Ошибка: {str(e)}") + async def setup_rooms(update: Update, context): """Настроить номера отеля.""" query = update.callback_query diff --git a/pms_integration/plugins/ecvi_pms.py b/pms_integration/plugins/ecvi_pms.py index 4caec542..cf3f642e 100644 --- a/pms_integration/plugins/ecvi_pms.py +++ b/pms_integration/plugins/ecvi_pms.py @@ -1,11 +1,11 @@ from .base_plugin import BasePMSPlugin - +import requests class EcviPMS(BasePMSPlugin): """ - Плагин для PMS Shelter. + Плагин для PMS ECVI. """ - def fetch_data(self): + def _fetch_data(self): print("Fetching data from Ecvi PMS...") # Реализация метода получения данных из PMS Shelter response = requests.get(self.pms_config.url, headers={"Authorization": f"Bearer {self.pms_config.token}"}) diff --git a/pms_integration/plugins/shelter_pms.py b/pms_integration/plugins/shelter_pms.py index a6c04bf0..164a0f08 100644 --- a/pms_integration/plugins/shelter_pms.py +++ b/pms_integration/plugins/shelter_pms.py @@ -16,34 +16,32 @@ class Shelter(BasePMSPlugin): self.api_url = pms_config.url self.token = pms_config.token self.pagination_count = 50 - def get_default_parser_settings(self): - """ - Возвращает настройки по умолчанию для разбора данных PMS Shelter. - """ - return { - "fields_mapping": { - "reservation_id": "id", - "hotel_id": "hotelId", - "hotel_name": "hotelName", - "check_in": "from", - "check_out": "until", - "reservation_date": "date", - "room_type_id": "roomTypeId", - "room_id": "roomId", - "room_number": "roomNumber", - "room_type_name": "roomTypeName", - "check_in_status": "checkInStatus", - "is_annul": "isAnnul", - "tariff_id": "tariffId", - "reservation_price": "reservationPrice", - "discount": "discount", - "guests": "guests", - }, - "date_format": "%Y-%m-%dT%H:%M:%S", - "timezone": "UTC", - } + """ + Возвращает настройки по умолчанию для разбора данных PMS Shelter. + """ + return { + "fields_mapping": { + "reservation_id": "id", + "hotel_id": "hotelId", + "hotel_name": "hotelName", + "check_in": "from", + "check_out": "until", + "reservation_date": "date", + "room_type_id": "roomTypeId", + "room_id": "roomId", + "room_number": "roomNumber", + "room_type_name": "roomTypeName", + "check_in_status": "checkInStatus", + "is_annul": "isAnnul", + "reservation_price": "reservationPrice", + "discount": "discount", + }, + "date_format": "%Y-%m-%dT%H:%M:%S", + "timezone": "UTC", + } + async def _get_last_saved_date(self): """ Получает дату последнего сохраненного бронирования для отеля. @@ -56,127 +54,159 @@ class Shelter(BasePMSPlugin): except Exception as e: print(f"[ERROR] Ошибка получения последнего сохраненного бронирования: {e}") return None - async def _fetch_data(self): """ - Получает данные о бронированиях из PMS Shelter. + Получает данные о бронированиях из PMS. + Данные обрабатываются по временным промежуткам и сразу записываются в БД. + Возвращает отчёт о проделанной работе. """ - try: - now = datetime.utcnow() - start_date = await self._get_last_saved_date() or (now - timedelta(days=60)) - end_date = now + timedelta(days=60) - total_count = None - from_index = 0 + now = datetime.utcnow().replace(tzinfo=timezone.utc) + start_date = await self._get_last_saved_date() or (now - timedelta(days=90)) + end_date = now + timedelta(days=30) - headers = { - 'accept': 'text/plain', - 'Authorization': f'Bearer {self.token}', - 'Content-Type': 'application/json', + headers = { + 'accept': 'text/plain', + 'Authorization': f'Bearer {self.token}', + 'Content-Type': 'application/json', + } + + print(f"[DEBUG] Fetching data from {start_date} to {end_date}") + + # Результаты выполнения + report = { + "processed_intervals": 0, + "processed_items": 0, + "errors": [], + } + + # Разделение на временные интервалы + interval_days = 5 # Например, каждые 5 дней + current_start_date = start_date + + while current_start_date < end_date: + current_end_date = min(current_start_date + timedelta(days=interval_days), end_date) + + # Формирование payload + payload = { + "from": current_start_date.strftime('%Y-%m-%dT%H:%M:%SZ'), + "until": current_end_date.strftime('%Y-%m-%dT%H:%M:%SZ'), } + print(f"[DEBUG] Sending payload: {json.dumps(payload)}") - print(f"[DEBUG] Start date: {start_date}, End date: {end_date}") + try: + response = await sync_to_async(requests.post)(self.api_url, headers=headers, data=json.dumps(payload)) + response.raise_for_status() + except requests.exceptions.RequestException as e: + error_message = f"[ERROR] Request error between {current_start_date} and {current_end_date}: {e}" + print(error_message) + report["errors"].append(error_message) + current_start_date = current_end_date + continue - while total_count is None or from_index < total_count: - payload = { - "from": start_date.strftime('%Y-%m-%dT%H:%M:%SZ'), - "until": end_date.strftime('%Y-%m-%dT%H:%M:%SZ'), - "pagination": { - "from": from_index, - "count": self.pagination_count, - }, - } - print(f"[DEBUG] Payload: {json.dumps(payload)}") + try: + data = response.json() + print(f"[DEBUG] Received response: {data}") + except json.JSONDecodeError as e: + error_message = f"[ERROR] Ошибка декодирования JSON между {current_start_date} и {current_end_date}: {e}" + print(error_message) + report["errors"].append(error_message) + current_start_date = current_end_date + continue - try: - response = await sync_to_async(requests.post)(self.api_url, headers=headers, data=json.dumps(payload)) - except requests.RequestException as e: - print(f"[ERROR] Ошибка HTTP-запроса: {e}") - raise ValueError(f"Ошибка HTTP-запроса: {e}") - - print(f"[DEBUG] Response status: {response.status_code}") - - if response.status_code != 200: - print(f"[ERROR] Request error: {response.status_code}, {response.text}") - raise ValueError(f"Ошибка запроса: {response.status_code}, {response.text}") - - try: - data = response.json() - except json.JSONDecodeError as e: - print(f"[ERROR] Ошибка декодирования JSON: {e}") - raise ValueError(f"Ошибка декодирования JSON: {e}") - - # Проверяем, что ответ содержит ключи "count" и "items" - if not isinstance(data, dict) or "count" not in data or "items" not in data: - print(f"[ERROR] Неверный формат данных: {data}") - raise ValueError(f"Неверный формат данных: {data}") - - total_count = data.get("count", 0) - items = data.get("items", []) - - print(f"[DEBUG] Total count: {total_count}, Items retrieved: {len(items)}") - - if not isinstance(items, list): - print(f"[ERROR] Неверный тип items: {type(items)}. Ожидался list.") - raise ValueError(f"Неверный тип items: {type(items)}. Ожидался list.") - - for item in items: - if not isinstance(item, dict): - print(f"[ERROR] Неверный формат элемента items: {item}") - continue + total_count = data.get("count", 0) + items = data.get("items", []) + print(f"[DEBUG] Retrieved {len(items)} items (Total: {total_count}).") + # Если данных нет, пропускаем текущий интервал + if not items: + print(f"[WARNING] No items found between {current_start_date} and {current_end_date}.") + else: + for idx, item in enumerate(items, start=1): + print(f"[DEBUG] Processing item #{idx}/{len(items)}: {item}") try: await self._save_to_db(item) + report["processed_items"] += 1 except Exception as e: - print(f"[ERROR] Ошибка сохранения бронирования {item.get('id')}: {e}") + error_message = f"[ERROR] Error processing item {item.get('id', 'Unknown')}: {e}" + print(error_message) + report["errors"].append(error_message) - from_index += len(items) - print(f"[DEBUG] Updated from_index: {from_index}") + # Отмечаем обработанный интервал + report["processed_intervals"] += 1 - except Exception as e: - print(f"[ERROR] Общая ошибка в методе _fetch_data: {e}") + # Обновляем начало интервала + current_start_date = current_end_date + print(f"[DEBUG] Data fetching completed from {start_date} to {end_date}.") + return report + async def _save_to_db(self, item): """ Сохраняет данные о бронировании в БД. + Проверяет, существует ли уже бронирование с таким ID. """ try: - print(f"[DEBUG] Fetching hotel for PMS: {self.pms_config}") + print(f"[DEBUG] Starting to save reservation {item['id']} to the database.") + hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config) print(f"[DEBUG] Hotel found: {hotel.name}") - # Учитываем формат даты без 'Z' date_format = '%Y-%m-%dT%H:%M:%S' print(f"[DEBUG] Parsing check-in and check-out dates for reservation {item['id']}") - check_in = datetime.strptime(item["from"], date_format).replace(tzinfo=timezone.utc) - check_out = datetime.strptime(item["until"], date_format).replace(tzinfo=timezone.utc) + try: + check_in = datetime.strptime(item["from"], date_format).replace(tzinfo=timezone.utc) + check_out = datetime.strptime(item["until"], date_format).replace(tzinfo=timezone.utc) + print(f"[DEBUG] Parsed check-in: {check_in}, check-out: {check_out}") + except Exception as e: + print(f"[ERROR] Ошибка парсинга дат для бронирования {item['id']}: {e}") + raise - # Проверяем room_number и устанавливаем значение по умолчанию, если оно отсутствует room_number = item.get("roomNumber", "") or "Unknown" print(f"[DEBUG] Room number determined: {room_number}") - # Сохраняем бронирование - print(f"[DEBUG] Saving reservation {item['id']} to database") - reservation, created = await sync_to_async(Reservation.objects.update_or_create)( - reservation_id=item["id"], - hotel=hotel, - defaults={ - "room_number": room_number, - "room_type": item.get("roomTypeName", ""), - "check_in": check_in, - "check_out": check_out, - "status": item.get("checkInStatus", ""), - "price": item.get("reservationPrice", 0), - "discount": item.get("discount", 0), - }, - ) + print(f"[DEBUG] Checking if reservation with ID {item['id']} already exists.") + existing_reservation = await sync_to_async( + Reservation.objects.filter(reservation_id=item["id"]).first + )() - print(f"[DEBUG] {'Created' if created else 'Updated'} reservation {item['id']}") + if existing_reservation: + print(f"[DEBUG] Reservation with ID {item['id']} already exists. Updating it...") + print(f"[DEBUG] Updating existing reservation {item['id']}.") + await sync_to_async(Reservation.objects.update_or_create)( + reservation_id=item["id"], + hotel=hotel, + defaults={ + "room_number": room_number, + "room_type": item.get("roomTypeName", ""), + "check_in": check_in, + "check_out": check_out, + "status": item.get("checkInStatus", ""), + "price": item.get("reservationPrice", 0), + "discount": item.get("discount", 0), + }, + ) + print(f"[DEBUG] Updated existing reservation {item['id']}.") + else: + print(f"[DEBUG] No existing reservation found for ID {item['id']}. Creating a new one...") + print(f"[DEBUG] Creating a new reservation {item['id']}.") + await sync_to_async(Reservation.objects.create)( + reservation_id=item["id"], + hotel=hotel, + room_number=room_number, + room_type=item.get("roomTypeName", ""), + check_in=check_in, + check_out=check_out, + status=item.get("checkInStatus", ""), + price=item.get("reservationPrice", 0), + discount=item.get("discount", 0), + ) + print(f"[DEBUG] Created a new reservation {item['id']}.") + + print(f"[DEBUG] {'Created' if not existing_reservation else 'Updated'} reservation {item['id']} / {hotel.name}.") - except KeyError as ke: - print(f"[ERROR] Ошибка обработки ключей в элементе {item}: {ke}") except ValueError as ve: - print(f"[ERROR] Ошибка обработки данных для бронирования {item['id']}: {ve}") + print(f"[ERROR] Ошибка обработки даты для бронирования {item['id']}: {ve}") except Exception as e: - print(f"[ERROR] Общая ошибка сохранения бронирования {item.get('id', 'Unknown')}: {e}") + print(f"[ERROR] Ошибка сохранения бронирования {item['id']}: {e}") diff --git a/reports/GoldenHills 4_report.pdf b/reports/GoldenHills 4_report.pdf index 9a589418341a03fc64f92540acbf0af144d54882..e057a3038f8077a30b32922b008178e62027ef85 100644 GIT binary patch delta 9920 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 UA-N@y**V$SkZ5Qm6eW@V53tB2rvLx| delta 11051 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