import requests import json from datetime import datetime, timedelta, timezone from asgiref.sync import sync_to_async from hotels.models import Reservation, Hotel from .base_plugin import BasePMSPlugin from pms_integration.models import PMSConfiguration class Shelter(BasePMSPlugin): """ Плагин для интеграции с Shelter PMS. """ def __init__(self, pms_config): super().__init__(pms_config) 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", "reservation_price": "reservationPrice", "discount": "discount", }, "date_format": "%Y-%m-%dT%H:%M:%S", "timezone": "UTC", } async def _get_last_saved_date(self): """ Получает дату последнего сохраненного бронирования для отеля. """ try: last_reservation = await sync_to_async( Reservation.objects.filter(hotel__pms=self.pms_config).order_by('-check_in').first )() return last_reservation.check_in if last_reservation else None except Exception as e: print(f"[ERROR] Ошибка получения последнего сохраненного бронирования: {e}") return None async def _fetch_data(self): """ Получает данные о бронированиях из PMS. Данные обрабатываются по временным промежуткам и сразу записываются в БД. Возвращает отчёт о проделанной работе. """ 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', } 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)}") 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 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 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: error_message = f"[ERROR] Error processing item {item.get('id', 'Unknown')}: {e}" print(error_message) report["errors"].append(error_message) # Отмечаем обработанный интервал report["processed_intervals"] += 1 # Обновляем начало интервала 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] 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}") date_format = '%Y-%m-%dT%H:%M:%S' print(f"[DEBUG] Parsing check-in and check-out dates for reservation {item['id']}") 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 = item.get("roomNumber", "") or "Unknown" print(f"[DEBUG] Room number determined: {room_number}") 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 )() 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 ValueError as ve: print(f"[ERROR] Ошибка обработки даты для бронирования {item['id']}: {ve}") except Exception as e: print(f"[ERROR] Ошибка сохранения бронирования {item['id']}: {e}")