From 4a16de10245e13c1f889df8ff6e1fa256580fe65 Mon Sep 17 00:00:00 2001 From: trevor Date: Wed, 25 Dec 2024 15:50:33 +0900 Subject: [PATCH 1/3] Bnovo pms --- hotels/models.py | 1 + pms_integration/models.py | 6 +- pms_integration/plugins/bnovo_pms.py | 227 +++++++++++++----- pms_integration/plugins/realtycalendar_pms.py | 191 --------------- pms_integration/plugins/trevelline_pms.py | 116 --------- 5 files changed, 171 insertions(+), 370 deletions(-) delete mode 100644 pms_integration/plugins/trevelline_pms.py diff --git a/hotels/models.py b/hotels/models.py index 63c39c76..d2d9c9d6 100644 --- a/hotels/models.py +++ b/hotels/models.py @@ -23,6 +23,7 @@ class APIConfiguration(models.Model): class Hotel(models.Model): name = models.CharField(max_length=255, verbose_name="Название отеля") hotel_id = models.CharField(max_length=255, unique=True, null=True, blank=True, verbose_name="ID отеля") + external_id_pms = models.CharField(max_length=100, unique=False, null=True, blank=True, verbose_name="Внешний PMS ID") created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан") phone = models.CharField( max_length=50, diff --git a/pms_integration/models.py b/pms_integration/models.py index 63636bb0..8f37b9db 100644 --- a/pms_integration/models.py +++ b/pms_integration/models.py @@ -14,7 +14,11 @@ class PMSConfiguration(models.Model): password = models.CharField(max_length=255, blank=True, null=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="Дата создания") - + hotels = models.ManyToManyField( + 'hotels.Hotel', + related_name='pms_configurations', + verbose_name="Отели" + ) def __str__(self): return self.name diff --git a/pms_integration/plugins/bnovo_pms.py b/pms_integration/plugins/bnovo_pms.py index 673664b5..dc5fc251 100644 --- a/pms_integration/plugins/bnovo_pms.py +++ b/pms_integration/plugins/bnovo_pms.py @@ -1,136 +1,239 @@ import requests import json from datetime import datetime, timedelta -from .base_plugin import BasePMSPlugin from asgiref.sync import sync_to_async +from .base_plugin import BasePMSPlugin from pms_integration.models import PMSConfiguration +from touchh.utils.log import CustomLogger +from pms_integration.models import PMSConfiguration +from hotels.models import Hotel, Reservation +logger = CustomLogger(name="BnovoPMS Plugin", log_level="DEBUG").get_logger() class BnovoPMSPlugin(BasePMSPlugin): """Плагин для работы с PMS Bnovo.""" def __init__(self, config): super().__init__(config) - self.api_url = config.url.rstrip("/") # Убираем лишний `/` в конце URL + self.api_url = config.url.rstrip("/") self.username = config.username self.password = config.password - self.token = None # SID + self.token = None if not self.api_url: + logger.error("Не указан URL для работы плагина.") raise ValueError("Не указан URL для работы плагина.") if not self.username or not self.password: + logger.error("Не указаны логин или пароль для авторизации.") raise ValueError("Не указаны логин или пароль для авторизации.") def get_default_parser_settings(self): """Возвращает настройки по умолчанию для обработки данных.""" + logger.debug("Получение настроек парсера по умолчанию.") return { "date_format": "%Y-%m-%dT%H:%M:%S", "timezone": "UTC" } - async def _save_token_to_db(self, sid): - """Сохраняет токен (SID) в базу данных.""" + async def _get_stored_token(self, hotel_id): + """Получение токена из базы данных для конкретного отеля.""" try: + logger.debug(f"Попытка получения токена из базы данных для отеля: {hotel_id}.") + config = await sync_to_async(PMSConfiguration.objects.get)(plugin_name="bnovo", hotel_id=hotel_id) + logger.debug(f"Токен из базы данных: {config.token}") + return config.token + except PMSConfiguration.DoesNotExist: + logger.warning(f"Токен отсутствует в базе данных для отеля: {hotel_id}.") + return None + + async def _save_token_to_db(self, sid, hotel_id): + """Сохраняет токен (SID) в базу данных для конкретного отеля.""" + try: + logger.debug(f"Сохранение токена в базу данных для отеля {hotel_id}: {sid}") await sync_to_async(PMSConfiguration.objects.update_or_create)( - plugin_name="bnovo", - defaults={"token": sid} + plugin_name="bnovo", hotel_id=hotel_id, defaults={"token": sid} ) - print(f"[DEBUG] Токен сохранен в БД: {sid}") + logger.debug("Токен успешно сохранен.") except Exception as e: - print(f"[ERROR] Ошибка сохранения токена в БД: {e}") + logger.error(f"Ошибка сохранения токена в базу данных для отеля {hotel_id}: {e}") def _get_auth_headers(self): """Создает заголовки авторизации.""" + logger.debug("Создание заголовков авторизации.") headers = { "Content-Type": "application/json", "Accept": "application/json", } if self.token: headers["Cookie"] = f"SID={self.token}" + logger.debug(f"Добавлен токен в заголовки: {self.token}") return headers - async def _fetch_session(self): - """Получает идентификатор сессии (SID) через запрос.""" + async def _fetch_session(self, hotel_id): + """Получение нового токена (SID) через запрос для конкретного отеля.""" url = f"{self.api_url}/" - payload = { - "username": self.username, - "password": self.password, - } - - print(f"[DEBUG] URL авторизации: {url}") - print(f"[DEBUG] Тело запроса: {json.dumps(payload, indent=2)}") + payload = {"username": self.username, "password": self.password} headers = self._get_auth_headers() - session = requests.Session() - response = session.post(url, json=payload, headers=headers, allow_redirects=False) + logger.debug(f"Авторизация по адресу: {url} с данными: {json.dumps(payload)}") + response = requests.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}") + logger.debug(f"Ответ авторизации: статус {response.status_code}, заголовки {response.headers}") + if response.status_code == 302: + cookies = response.cookies.get_dict() + sid = cookies.get("SID") + if sid: + self.token = sid + logger.debug(f"Получен новый SID: {sid}") + await self._save_token_to_db(sid, hotel_id) + else: + logger.error("Не удалось извлечь SID из ответа.") + raise ValueError("Не удалось извлечь SID из ответа.") else: - raise ValueError(f"Не удалось получить SID из ответа: {response.text}") + logger.error(f"Ошибка авторизации: {response.status_code}, {response.text}") + raise ValueError(f"Ошибка авторизации: {response.status_code}, {response.text}") + async def _fetch_account_data(self, hotel_id): + """Получение данных аккаунта через эндпоинт /account/current для конкретного отеля.""" + logger.info(f"Начало получения данных аккаунта для отеля: {hotel_id}.") + self.token = await self._get_stored_token(hotel_id) + + if not self.token: + logger.info("Токен отсутствует, выполняем авторизацию.") + await self._fetch_session(hotel_id) + + url = f"{self.api_url}/account/current" + headers = self._get_auth_headers() + + logger.debug(f"Выполнение запроса к {url}") + response = requests.get(url, headers=headers) + + if response.status_code != 200: + logger.error(f"Ошибка при запросе данных аккаунта: {response.status_code}, {response.text}") + raise ValueError("Ошибка запроса к /account/current") + + try: + account_data = response.json() + logger.debug(f"Полученные данные аккаунта: {json.dumps(account_data, indent=2)}") + except json.JSONDecodeError as e: + logger.error(f"Ошибка декодирования JSON: {e}") + raise ValueError(f"Ошибка декодирования JSON: {e}") + + return account_data + + async def fetch_and_log_account_data(self, hotel_id): + """Вызов метода _fetch_account_data и вывод результата в лог.""" + logger.info(f"Запуск получения и логирования данных аккаунта для отеля: {hotel_id}.") + try: + account_data = await self._fetch_account_data(hotel_id) + logger.info(f"Успешно полученные данные аккаунта: {json.dumps(account_data, indent=2)}") + return account_data + except Exception as e: + logger.error(f"Ошибка при получении данных аккаунта: {e}") + raise + + async def fetch_data_with_account_info(self, hotel_id): + """Получение данных аккаунта и бронирований для конкретного отеля.""" + logger.info(f"Запуск процесса получения данных аккаунта и бронирований для отеля: {hotel_id}.") + try: + account_data = await self.fetch_and_log_account_data(hotel_id) + logger.info("Данные аккаунта успешно получены, продолжение с бронированиями.") + await self.__fetch_data() + except Exception as e: + logger.error(f"Ошибка при выполнении полной операции: {e}") + async def _fetch_data(self): - """Получает данные о бронированиях с помощью эндпоинта `/dashboard`.""" - await self._fetch_session() # Авторизуемся перед каждым запросом + """Получение данных о бронированиях с помощью эндпоинта /dashboard.""" + # Проверяем наличие токена в базе данных + logger.info("Начало процесса получения данных.") + self.token = await self._get_stored_token() + if not self.token: + logger.info("Токен отсутствует, выполняем авторизацию.") + await self._fetch_session() + + + accounts = await self._fetch_account_data() + print(f'\n------\nACCOUNTS: {accounts}\n-------\n') + + url = f"{self.api_url}/dashboard" now = datetime.now() - create_from = (now - timedelta(days=90)).strftime("%d.%m.%Y") # Диапазон: последние 90 дней + create_from = (now - timedelta(days=90)).strftime("%d.%m.%Y") 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", # Сортировка по возрастанию даты создания + "advanced_search": 2, + "c": 100, + "page": 1, + "order_by": "create_date.asc", } - headers = self._get_auth_headers() - all_bookings = [] # Для сохранения всех бронирований + logger.debug(f"Начальный запрос к {url} с параметрами: {json.dumps(params, indent=2)}") + 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) + logger.debug(f"Выполнение запроса к {url}") + response = requests.get(url, headers=headers, params=params) - print(f"[DEBUG] Статус ответа: {response.status_code}") + logger.debug(f"Ответ запроса: статус {response.status_code}, тело {response.text}") if response.status_code != 200: - raise ValueError(f"Ошибка при получении данных: {response.status_code}, {response.text}") + logger.error(f"Ошибка при запросе: {response.status_code}, {response.text}") + raise ValueError("Ошибка запроса к /dashboard") + + try: + data = response.json() + logger.debug(f"Полученные данные: {json.dumps(data, indent=2)}") + except json.JSONDecodeError as e: + logger.error(f"Ошибка декодирования JSON: {e}") + raise ValueError(f"Ошибка декодирования JSON: {e}") - 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)}") + logger.info(f"Получено бронирований за запрос: {len(bookings)}. Всего: {len(all_bookings)}.") - # Проверка на наличие следующей страницы + # Проверяем наличие следующей страницы pages_info = data.get("pages", {}) current_page = pages_info.get("current_page", 1) total_pages = pages_info.get("total_pages", 1) + logger.debug(f"Информация о страницах: текущая {current_page}, всего {total_pages}") if current_page >= total_pages: - break # Все страницы загружены + break - params["page"] += 1 # Переход на следующую страницу + 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 + # Сопоставляем бронирования с существующими записями + for booking in all_bookings: + booking_id = booking.get("id") + hotel_id = booking.get("hotel_id") + if not booking_id or not hotel_id: + logger.warning("У бронирования отсутствует id или hotel_id. Пропуск.") + continue + + if hotel_id != str(self.config.hotel.external_id_pms): + logger.debug(f"Бронирование {booking_id} не относится к отелю {self.config.hotel.external_id_pms}. Пропуск.") + continue + + reservation, created = await sync_to_async(Reservation.objects.update_or_create)( + external_id=booking_id, + defaults={ + "hotel": self.config.hotel, + "status": booking.get("status_name"), + "create_date": booking.get("create_date"), + "arrival": booking.get("arrival"), + "departure": booking.get("departure"), + "room_type": booking.get("initial_room_type_name"), + "data": booking + } + ) + + if created: + logger.info(f"Создана новая запись бронирования: {reservation}") + else: + logger.info(f"Обновлено существующее бронирование: {reservation}") + + logger.info(f"Все бронирования получены и обработаны. Итоговое количество: {len(all_bookings)}") + return all_bookings diff --git a/pms_integration/plugins/realtycalendar_pms.py b/pms_integration/plugins/realtycalendar_pms.py index d7955ff2..0b440aef 100644 --- a/pms_integration/plugins/realtycalendar_pms.py +++ b/pms_integration/plugins/realtycalendar_pms.py @@ -1,194 +1,3 @@ -# import requests -# import hashlib -# import json -# from .base_plugin import BasePMSPlugin -# from datetime import datetime, timedelta -# from asgiref.sync import sync_to_async -# from touchh.utils.log import CustomLogger -# from hotels.models import Hotel, Reservation -# from app_settings.models import GlobalHotelSettings -# from django.utils import timezone -# class RealtyCalendarPlugin(BasePMSPlugin): -# 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("/") -# self.logger = CustomLogger(name="RealtyCalendarPlugin", log_level="DEBUG").get_logger() -# 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): -# """ -# Возвращает отсортированный по имени список ключей. -# """ -# sorted_keys = sorted(obj.keys()) -# self.logger.debug(f"Отсортированные ключи: {sorted_keys}") -# return sorted_keys - -# def _generate_data_string(self, obj): -# """ -# Формирует строку параметров для подписи. -# """ -# sorted_keys = self._get_sorted_keys(obj) -# string = "".join(f"{key}={obj[key]}" for key in sorted_keys) -# self.logger.debug(f"Сформированная строка данных: {string}") -# return string + self.private_key - -# def _generate_md5(self, string): -# """ -# Генерирует MD5-хеш от строки. -# """ -# md5_hash = hashlib.md5(string.encode("utf-8")).hexdigest() -# self.logger.debug(f"Сформированный MD5-хеш: {md5_hash}") -# return md5_hash - -# def _generate_sign(self, data): -# """ -# Генерирует подпись для данных запроса. -# """ -# data_string = self._generate_data_string(data) -# self.logger.debug(f"Строка для подписи: {data_string}") -# sign = self._generate_md5(data_string) -# self.logger.debug(f"Подпись: {sign}") -# return sign - -# async def _fetch_data(self): -# """ -# Выполняет запрос к API RealtyCalendar для получения данных о бронированиях. -# """ -# self.logger.debug("Начало выполнения функции _fetch_data") -# base_url = f"{self.api_url}/api/v1/bookings/{self.public_key}/" -# headers = { -# "Accept": "application/json", -# "Content-Type": "application/json", -# } - -# now = datetime.now() -# data = { -# "begin_date": (now - timedelta(days=7)).strftime("%Y-%m-%d"), -# "end_date": now.strftime("%Y-%m-%d"), -# } -# data["sign"] = self._generate_sign(data) - -# response = requests.post(url=base_url, headers=headers, json=data) -# self.logger.debug(f"Статус ответа: {response.status_code}") - -# if response.status_code != 200: -# self.logger.error(f"Ошибка API: {response.status_code}, {response.text}") -# raise ValueError(f"Ошибка API RealtyCalendar: {response.status_code}") - -# try: -# response_data = response.json() -# bookings = response_data.get("bookings", []) -# if not isinstance(bookings, list): -# raise ValueError(f"Ожидался список, но получен {type(bookings)}") -# except Exception as e: -# self.logger.error(f"Ошибка обработки ответа API: {e}") -# raise - -# # Получаем глобальные настройки отеля -# hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config) -# hotel_tz = hotel.timezone -# try: -# hotel_settings = await sync_to_async(GlobalHotelSettings.objects.first)() -# check_in_time = hotel_settings.check_in_time.strftime("%H:%M:%S") -# check_out_time = hotel_settings.check_out_time.strftime("%H:%M:%S") -# except AttributeError: -# # Используем значения по умолчанию, если настроек нет -# check_in_time = "14:00:00" -# check_out_time = "12:00:00" - -# filtered_data = [ -# { -# 'reservation_id': item.get('id'), -# 'checkin': timezone.make_aware( -# datetime.strptime( -# f"{item.get('begin_date')} {check_in_time}", -# "%Y-%m-%d %H:%M:%S" -# ) -# ), -# 'checkout': timezone.make_aware( -# datetime.strptime( -# f"{item.get('end_date')} {check_out_time}", -# "%Y-%m-%d %H:%M:%S" -# ) -# ), -# 'room_number': item.get('apartment_id'), -# 'room_type': item.get('notes', 'Описание отсутствует'), -# 'status': item.get('status') -# } -# for item in bookings -# if isinstance(item, dict) and item.get("status") in ["booked", "request"] -# ] - -# await self._save_to_db(filtered_data) - -# async def _save_to_db(self, data): -# """ -# Сохраняет данные в БД (например, информацию о номере). -# """ -# if not isinstance(data, list): -# self.logger.error(f"Ожидался список записей, но получен {type(data).__name__}") -# return - -# for index, item in enumerate(data, start=1): -# try: -# hotel = await sync_to_async(Hotel.objects.get)(pms=self.pms_config) -# reservation_id = item.get('reservation_id') -# if not reservation_id: -# self.logger.error(f"Пропущена запись {index}: отсутствует 'id'") -# continue - -# existing_reservation = await sync_to_async(Reservation.objects.filter)(reservation_id=reservation_id) -# existing_reservation = await sync_to_async(existing_reservation.first)() - -# defaults = { -# 'room_number': item['room_number'], -# 'room_type': item['room_type'], -# 'check_in': item['checkin'], -# 'check_out': item['checkout'], -# 'status': item['status'], -# 'hotel': hotel -# } - -# if existing_reservation: -# await sync_to_async(Reservation.objects.update_or_create)( -# reservation_id=reservation_id, defaults=defaults -# ) -# self.logger.debug(f"Резервация {reservation_id} обновлена.") -# else: -# await sync_to_async(Reservation.objects.create)( -# reservation_id=reservation_id, **defaults -# ) -# self.logger.debug(f"Создана новая резервация {reservation_id}") - -# except Exception as e: -# self.logger.error(f"Ошибка при обработке записи {index}: {e}") - -# def validate_plugin(self): -# """ -# Проверка на соответствие требованиям. -# Можно проверить наличие методов или полей. -# """ -# # Проверяем наличие обязательных методов -# required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"] -# for m in required_methods: -# if not hasattr(self, m): -# raise ValueError(f"Плагин {type(self).__name__} не реализует метод {m}.") -# self.logger.debug(f"Плагин {self.__class__.__name__} прошел валидацию.") -# return True - - import requests import hashlib import json diff --git a/pms_integration/plugins/trevelline_pms.py b/pms_integration/plugins/trevelline_pms.py deleted file mode 100644 index 6d1b22a5..00000000 --- a/pms_integration/plugins/trevelline_pms.py +++ /dev/null @@ -1,116 +0,0 @@ -import logging -import requests -from datetime import datetime -from .base_plugin import BasePMSPlugin - -class TravelLinePMSPlugin(BasePMSPlugin): - """ - Плагин для интеграции с PMS TravelLine. - """ - BASE_URL = "https://partner.tlintegration.com/api/webpms/v1" - - def __init__(self, pms_config): - """ - Инициализация плагина с конфигурацией PMS. - - :param pms_config: Конфигурация PMS (объект PMSConfiguration). - """ - super().__init__(pms_config) - self.api_key = pms_config.api_key - self.logger = logging.getLogger(self.__class__.__name__) - - def _get_headers(self): - """ - Возвращает заголовки для запросов. - """ - return { - "Authorization": f"Bearer {self.api_key}", - "Content-Type": "application/json" - } - - def _fetch_data(self): - """ - Получение данных из API TravelLine (поиск бронирований). - - :return: Список номеров бронирований. - """ - url = f"{self.BASE_URL}/bookings" - params = { - "roomId": self.pms_config.room_id, - "modifiedFrom": self.pms_config.modified_from, - "modifiedTo": self.pms_config.modified_to, - "state": self.pms_config.state, - "affectsPeriodFrom": self.pms_config.affects_period_from, - "affectsPeriodTo": self.pms_config.affects_period_to, - } - params = {k: v for k, v in params.items() if v is not None} - - try: - response = requests.get(url, headers=self._get_headers(), params=params) - response.raise_for_status() - self.logger.info("Данные успешно получены из API TravelLine.") - return response.json().get("bookingNumbers", []) - except requests.RequestException as e: - self.logger.error(f"Ошибка при запросе к API TravelLine: {e}") - return [] - - def fetch_data(self): - """ - Обертка для получения данных из API TravelLine с дополнительной обработкой. - - :return: Список номеров бронирований. - """ - return self._fetch_data() - - def get_default_parser_settings(self): - """ - Возвращает настройки парсера по умолчанию. - """ - return { - "field_mapping": { - "reservation_id": "bookingNumber", - "check_in": "actualCheckInDateTime", - "check_out": "actualCheckOutDateTime", - "room_number": "roomId", - "status": "state", - }, - "date_format": "%Y-%m-%dT%H:%M" - } - - def process_data(self, booking_number, room_stay_id, action, actual_date_time): - """ - Обработка данных для заселения или выселения проживания. - - :param booking_number: Номер бронирования. - :param room_stay_id: Идентификатор проживания. - :param action: Действие ("check-in" или "check-out"). - :param actual_date_time: Фактические дата и время. - :return: Ответ API. - """ - if action not in ["check-in", "check-out"]: - raise ValueError("Invalid action. Must be 'check-in' or 'check-out'.") - - url = f"{self.BASE_URL}/bookings/{booking_number}/room-stays/{room_stay_id}/{action}" - payload = { - f"actual{action.capitalize()}DateTime": actual_date_time - } - - try: - response = requests.post(url, headers=self._get_headers(), json=payload) - response.raise_for_status() - self.logger.info(f"Успешно выполнено действие '{action}' для бронирования {booking_number}.") - return response.json() - except requests.RequestException as e: - self.logger.error(f"Ошибка при выполнении действия '{action}': {e}") - return {} - - def validate_plugin(self): - """ - Проверка плагина на соответствие требованиям. - """ - required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"] - for method in required_methods: - if not hasattr(self, method): - raise ValueError(f"Плагин {type(self).__name__} не реализует метод {method}.") - self.logger.info(f"Плагин {self.__class__.__name__} успешно прошел валидацию.") - return True From 32f142727ee161433551faad379b6958581485b5 Mon Sep 17 00:00:00 2001 From: trevor Date: Wed, 25 Dec 2024 15:51:36 +0900 Subject: [PATCH 2/3] bnovo --- antifroud/migrations/0001_initial.py | 129 ++++++++++++++++ antifroud/migrations/0002_initial.py | 32 ++++ app_settings/migrations/0001_initial.py | 86 +++++++++++ hotels/migrations/0001_initial.py | 138 ++++++++++++++++++ hotels/migrations/0002_initial.py | 47 ++++++ ...ernal_id_hotel_external_id_pms_and_more.py | 54 +++++++ pms_integration/migrations/0001_initial.py | 50 +++++++ .../0002_pmsconfiguration_hotels.py | 19 +++ pms_integration/plugins/travelline_pms.py | 116 +++++++++++++++ scheduler/migrations/0001_initial.py | 30 ++++ users/migrations/0001_initial.py | 63 ++++++++ 11 files changed, 764 insertions(+) create mode 100644 antifroud/migrations/0001_initial.py create mode 100644 antifroud/migrations/0002_initial.py create mode 100644 app_settings/migrations/0001_initial.py create mode 100644 hotels/migrations/0001_initial.py create mode 100644 hotels/migrations/0002_initial.py create mode 100644 hotels/migrations/0003_rename_external_id_hotel_external_id_pms_and_more.py create mode 100644 pms_integration/migrations/0001_initial.py create mode 100644 pms_integration/migrations/0002_pmsconfiguration_hotels.py create mode 100644 pms_integration/plugins/travelline_pms.py create mode 100644 scheduler/migrations/0001_initial.py create mode 100644 users/migrations/0001_initial.py diff --git a/antifroud/migrations/0001_initial.py b/antifroud/migrations/0001_initial.py new file mode 100644 index 00000000..5c8a43b8 --- /dev/null +++ b/antifroud/migrations/0001_initial.py @@ -0,0 +1,129 @@ +# Generated by Django 5.1.4 on 2024-12-25 04:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ExternalDBSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Имя подключения для идентификации.', max_length=255, unique=True)), + ('host', models.CharField(help_text='Адрес сервера базы данных.', max_length=255)), + ('port', models.PositiveIntegerField(default=3306, help_text='Порт сервера базы данных.')), + ('user', models.CharField(help_text='Имя пользователя базы данных.', max_length=255)), + ('password', models.CharField(help_text='Пароль для подключения.', max_length=255)), + ('database', models.CharField(default='u1510415_wp832', help_text='Имя базы данных.', max_length=255)), + ('table_name', models.CharField(blank=True, default='wpts_user_activity_log', help_text='Имя таблицы для загрузки данных.', max_length=255, null=True)), + ('selected_fields', models.TextField(blank=True, help_text='Список полей для загрузки (через запятую).', null=True)), + ('is_active', models.BooleanField(default=True, help_text='Флаг активности подключения.')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'verbose_name': 'Настройка подключения к БД', + 'verbose_name_plural': 'Настройки подключений к БД', + }, + ), + migrations.CreateModel( + name='ImportedHotel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('external_id', models.CharField(max_length=255, verbose_name='Внешний ID отеля')), + ('name', models.CharField(max_length=255, verbose_name='Имя отеля')), + ('display_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Отображаемое имя')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')), + ('imported', models.BooleanField(default=False, verbose_name='Импортирован в основную базу')), + ], + options={ + 'verbose_name': 'Импортированный отель', + 'verbose_name_plural': 'Импортированные отели', + }, + ), + migrations.CreateModel( + name='RoomDiscrepancy', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('room_number', models.CharField(max_length=50, verbose_name='Номер комнаты')), + ('booking_id', models.CharField(max_length=255, verbose_name='ID бронирования')), + ('check_in_date_expected', models.DateField(verbose_name='Ожидаемая дата заселения')), + ('check_in_date_actual', models.DateField(verbose_name='Фактическая дата заселения')), + ('discrepancy_type', models.CharField(choices=[('early', 'Раннее заселение'), ('late', 'Позднее заселение'), ('missed', 'Неявка')], max_length=50, verbose_name='Тип несоответствия')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')), + ], + options={ + 'verbose_name': 'Несовпадение в заселении', + 'verbose_name_plural': 'Несовпадения в заселении', + }, + ), + migrations.CreateModel( + name='SyncLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')), + ('recieved_records', models.IntegerField(default=0, verbose_name='Полученные записи')), + ('processed_records', models.IntegerField(default=0, verbose_name='Обработанные записи')), + ], + options={ + 'verbose_name': 'Журнал синхронизации', + 'verbose_name_plural': 'Журналы синхронизации', + }, + ), + migrations.CreateModel( + name='UserActivityLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('external_id', models.CharField(db_index=True, max_length=255, unique=True, verbose_name='Внешний ID')), + ('user_id', models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name='ID пользователя')), + ('ip', models.GenericIPAddressField(blank=True, db_index=True, null=True, verbose_name='IP-адрес')), + ('created', models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Дата создания')), + ('timestamp', models.BigIntegerField(blank=True, null=True, verbose_name='Метка времени')), + ('date_time', models.DateTimeField(blank=True, null=True, verbose_name='Дата и время')), + ('referred', models.TextField(blank=True, null=True, verbose_name='Реферальная ссылка')), + ('agent', models.TextField(blank=True, null=True, verbose_name='Агент пользователя')), + ('platform', models.CharField(blank=True, max_length=255, null=True, verbose_name='Платформа')), + ('version', models.CharField(blank=True, max_length=255, null=True, verbose_name='Версия')), + ('model', models.CharField(blank=True, max_length=255, null=True, verbose_name='Модель устройства')), + ('device', models.CharField(blank=True, max_length=255, null=True, verbose_name='Тип устройства')), + ('UAString', models.TextField(blank=True, null=True, verbose_name='User-Agent строка')), + ('location', models.CharField(blank=True, max_length=255, null=True, verbose_name='Местоположение')), + ('page_id', models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name='ID страницы')), + ('url_parameters', models.TextField(blank=True, null=True, verbose_name='Параметры URL')), + ('page_title', models.TextField(blank=True, null=True, verbose_name='Заголовок страницы')), + ('type', models.CharField(blank=True, max_length=50, null=True, verbose_name='Тип')), + ('last_counter', models.IntegerField(blank=True, null=True, verbose_name='Последний счетчик')), + ('hits', models.IntegerField(blank=True, default='0', null=True, verbose_name='Количество обращений')), + ('honeypot', models.BooleanField(blank=True, null=True, verbose_name='Метка honeypot')), + ('reply', models.BooleanField(blank=True, null=True, verbose_name='Ответ пользователя')), + ('page_url', models.URLField(blank=True, null=True, verbose_name='URL страницы')), + ], + options={ + 'verbose_name': 'Регистрация посетителей', + 'verbose_name_plural': 'Регистрации посетителей', + }, + ), + migrations.CreateModel( + name='ViolationLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('room_number', models.CharField(blank=True, max_length=50, null=True, verbose_name='Номер комнаты')), + ('violation_type', models.CharField(choices=[('missed', 'Неявка'), ('early', 'Раннее заселение'), ('late', 'Позднее заселение')], max_length=50, verbose_name='Тип нарушения')), + ('violation_details', models.TextField(blank=True, null=True, verbose_name='Детали нарушения')), + ('hits', models.IntegerField(verbose_name='Срабатывания')), + ('detected_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата обнаружения')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')), + ], + options={ + 'verbose_name': 'Журнал нарушений', + 'verbose_name_plural': 'Журналы нарушений', + }, + ), + ] diff --git a/antifroud/migrations/0002_initial.py b/antifroud/migrations/0002_initial.py new file mode 100644 index 00000000..49d027bc --- /dev/null +++ b/antifroud/migrations/0002_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 5.1.4 on 2024-12-25 04:55 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('antifroud', '0001_initial'), + ('hotels', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='roomdiscrepancy', + name='hotel', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'), + ), + migrations.AddField( + model_name='synclog', + name='hotel', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'), + ), + migrations.AddField( + model_name='violationlog', + name='hotel', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'), + ), + ] diff --git a/app_settings/migrations/0001_initial.py b/app_settings/migrations/0001_initial.py new file mode 100644 index 00000000..2509ab91 --- /dev/null +++ b/app_settings/migrations/0001_initial.py @@ -0,0 +1,86 @@ +# Generated by Django 5.1.4 on 2024-12-25 04:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='EmailSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('smtp_server', models.CharField(help_text='SMTP сервер для отправки почты', max_length=255)), + ('smtp_port', models.IntegerField(default=587, help_text='SMTP порт для почты')), + ('smtp_user', models.CharField(help_text='Имя пользователя для SMTP', max_length=255)), + ('smtp_password', models.CharField(help_text='Пароль для SMTP', max_length=255)), + ('from_email', models.EmailField(help_text='Email для отправки сообщений', max_length=254)), + ], + options={ + 'verbose_name': 'E-mail', + 'verbose_name_plural': 'E-mails', + }, + ), + migrations.CreateModel( + name='GlobalHotelSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('check_in_time', models.TimeField(help_text='Время заезда')), + ('check_out_time', models.TimeField(help_text='Время выезда')), + ('currency', models.CharField(help_text='Валюта', max_length=3)), + ('global_timezone', models.CharField(choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Timbuktu', 'Africa/Timbuktu'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/ComodRivadavia', 'America/Argentina/ComodRivadavia'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Atka', 'America/Atka'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Ensenada', 'America/Ensenada'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fort_Wayne', 'America/Fort_Wayne'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Knox_IN', 'America/Knox_IN'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montreal', 'America/Montreal'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Acre', 'America/Porto_Acre'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Rosario', 'America/Rosario'), ('America/Santa_Isabel', 'America/Santa_Isabel'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Shiprock', 'America/Shiprock'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Virgin', 'America/Virgin'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/South_Pole', 'Antarctica/South_Pole'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Ashkhabad', 'Asia/Ashkhabad'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Chongqing', 'Asia/Chongqing'), ('Asia/Chungking', 'Asia/Chungking'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Dacca', 'Asia/Dacca'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Harbin', 'Asia/Harbin'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Istanbul', 'Asia/Istanbul'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kashgar', 'Asia/Kashgar'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macao', 'Asia/Macao'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Tel_Aviv', 'Asia/Tel_Aviv'), ('Asia/Thimbu', 'Asia/Thimbu'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ujung_Pandang', 'Asia/Ujung_Pandang'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Ulan_Bator', 'Asia/Ulan_Bator'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Jan_Mayen', 'Atlantic/Jan_Mayen'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/ACT', 'Australia/ACT'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Canberra', 'Australia/Canberra'), ('Australia/Currie', 'Australia/Currie'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/LHI', 'Australia/LHI'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/NSW', 'Australia/NSW'), ('Australia/North', 'Australia/North'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Queensland', 'Australia/Queensland'), ('Australia/South', 'Australia/South'), ('Australia/Sydney', 'Australia/Sydney'), ('Australia/Tasmania', 'Australia/Tasmania'), ('Australia/Victoria', 'Australia/Victoria'), ('Australia/West', 'Australia/West'), ('Australia/Yancowinna', 'Australia/Yancowinna'), ('Brazil/Acre', 'Brazil/Acre'), ('Brazil/DeNoronha', 'Brazil/DeNoronha'), ('Brazil/East', 'Brazil/East'), ('Brazil/West', 'Brazil/West'), ('CET', 'CET'), ('CST6CDT', 'CST6CDT'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Canada/Saskatchewan', 'Canada/Saskatchewan'), ('Canada/Yukon', 'Canada/Yukon'), ('Chile/Continental', 'Chile/Continental'), ('Chile/EasterIsland', 'Chile/EasterIsland'), ('Cuba', 'Cuba'), ('EET', 'EET'), ('EST', 'EST'), ('EST5EDT', 'EST5EDT'), ('Egypt', 'Egypt'), ('Eire', 'Eire'), ('Etc/GMT', 'Etc/GMT'), ('Etc/GMT+0', 'Etc/GMT+0'), ('Etc/GMT+1', 'Etc/GMT+1'), ('Etc/GMT+10', 'Etc/GMT+10'), ('Etc/GMT+11', 'Etc/GMT+11'), ('Etc/GMT+12', 'Etc/GMT+12'), ('Etc/GMT+2', 'Etc/GMT+2'), ('Etc/GMT+3', 'Etc/GMT+3'), ('Etc/GMT+4', 'Etc/GMT+4'), ('Etc/GMT+5', 'Etc/GMT+5'), ('Etc/GMT+6', 'Etc/GMT+6'), ('Etc/GMT+7', 'Etc/GMT+7'), ('Etc/GMT+8', 'Etc/GMT+8'), ('Etc/GMT+9', 'Etc/GMT+9'), ('Etc/GMT-0', 'Etc/GMT-0'), ('Etc/GMT-1', 'Etc/GMT-1'), ('Etc/GMT-10', 'Etc/GMT-10'), ('Etc/GMT-11', 'Etc/GMT-11'), ('Etc/GMT-12', 'Etc/GMT-12'), ('Etc/GMT-13', 'Etc/GMT-13'), ('Etc/GMT-14', 'Etc/GMT-14'), ('Etc/GMT-2', 'Etc/GMT-2'), ('Etc/GMT-3', 'Etc/GMT-3'), ('Etc/GMT-4', 'Etc/GMT-4'), ('Etc/GMT-5', 'Etc/GMT-5'), ('Etc/GMT-6', 'Etc/GMT-6'), ('Etc/GMT-7', 'Etc/GMT-7'), ('Etc/GMT-8', 'Etc/GMT-8'), ('Etc/GMT-9', 'Etc/GMT-9'), ('Etc/GMT0', 'Etc/GMT0'), ('Etc/Greenwich', 'Etc/Greenwich'), ('Etc/UCT', 'Etc/UCT'), ('Etc/UTC', 'Etc/UTC'), ('Etc/Universal', 'Etc/Universal'), ('Etc/Zulu', 'Etc/Zulu'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belfast', 'Europe/Belfast'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Nicosia', 'Europe/Nicosia'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Tiraspol', 'Europe/Tiraspol'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GB', 'GB'), ('GB-Eire', 'GB-Eire'), ('GMT', 'GMT'), ('GMT+0', 'GMT+0'), ('GMT-0', 'GMT-0'), ('GMT0', 'GMT0'), ('Greenwich', 'Greenwich'), ('HST', 'HST'), ('Hongkong', 'Hongkong'), ('Iceland', 'Iceland'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Iran', 'Iran'), ('Israel', 'Israel'), ('Jamaica', 'Jamaica'), ('Japan', 'Japan'), ('Kwajalein', 'Kwajalein'), ('Libya', 'Libya'), ('MET', 'MET'), ('MST', 'MST'), ('MST7MDT', 'MST7MDT'), ('Mexico/BajaNorte', 'Mexico/BajaNorte'), ('Mexico/BajaSur', 'Mexico/BajaSur'), ('Mexico/General', 'Mexico/General'), ('NZ', 'NZ'), ('NZ-CHAT', 'NZ-CHAT'), ('Navajo', 'Navajo'), ('PRC', 'PRC'), ('PST8PDT', 'PST8PDT'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Johnston', 'Pacific/Johnston'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Samoa', 'Pacific/Samoa'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('Pacific/Yap', 'Pacific/Yap'), ('Poland', 'Poland'), ('Portugal', 'Portugal'), ('ROC', 'ROC'), ('ROK', 'ROK'), ('Singapore', 'Singapore'), ('Turkey', 'Turkey'), ('UCT', 'UCT'), ('US/Alaska', 'US/Alaska'), ('US/Aleutian', 'US/Aleutian'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/East-Indiana', 'US/East-Indiana'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Indiana-Starke', 'US/Indiana-Starke'), ('US/Michigan', 'US/Michigan'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('US/Samoa', 'US/Samoa'), ('UTC', 'UTC'), ('Universal', 'Universal'), ('W-SU', 'W-SU'), ('WET', 'WET'), ('Zulu', 'Zulu')], default='UTC', max_length=63)), + ], + options={ + 'verbose_name': 'Настройки отеля', + 'verbose_name_plural': 'Настройки отеля', + }, + ), + migrations.CreateModel( + name='GlobalSystemSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('system_name', models.CharField(help_text='Название системы', max_length=255)), + ('system_version', models.CharField(help_text='Версия системы', max_length=255)), + ('server_timezone', models.CharField(choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Timbuktu', 'Africa/Timbuktu'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/ComodRivadavia', 'America/Argentina/ComodRivadavia'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Atka', 'America/Atka'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Ensenada', 'America/Ensenada'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fort_Wayne', 'America/Fort_Wayne'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Knox_IN', 'America/Knox_IN'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montreal', 'America/Montreal'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Acre', 'America/Porto_Acre'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Rosario', 'America/Rosario'), ('America/Santa_Isabel', 'America/Santa_Isabel'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Shiprock', 'America/Shiprock'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Virgin', 'America/Virgin'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/South_Pole', 'Antarctica/South_Pole'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Ashkhabad', 'Asia/Ashkhabad'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Chongqing', 'Asia/Chongqing'), ('Asia/Chungking', 'Asia/Chungking'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Dacca', 'Asia/Dacca'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Harbin', 'Asia/Harbin'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Istanbul', 'Asia/Istanbul'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kashgar', 'Asia/Kashgar'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macao', 'Asia/Macao'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Tel_Aviv', 'Asia/Tel_Aviv'), ('Asia/Thimbu', 'Asia/Thimbu'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ujung_Pandang', 'Asia/Ujung_Pandang'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Ulan_Bator', 'Asia/Ulan_Bator'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Jan_Mayen', 'Atlantic/Jan_Mayen'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/ACT', 'Australia/ACT'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Canberra', 'Australia/Canberra'), ('Australia/Currie', 'Australia/Currie'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/LHI', 'Australia/LHI'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/NSW', 'Australia/NSW'), ('Australia/North', 'Australia/North'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Queensland', 'Australia/Queensland'), ('Australia/South', 'Australia/South'), ('Australia/Sydney', 'Australia/Sydney'), ('Australia/Tasmania', 'Australia/Tasmania'), ('Australia/Victoria', 'Australia/Victoria'), ('Australia/West', 'Australia/West'), ('Australia/Yancowinna', 'Australia/Yancowinna'), ('Brazil/Acre', 'Brazil/Acre'), ('Brazil/DeNoronha', 'Brazil/DeNoronha'), ('Brazil/East', 'Brazil/East'), ('Brazil/West', 'Brazil/West'), ('CET', 'CET'), ('CST6CDT', 'CST6CDT'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Canada/Saskatchewan', 'Canada/Saskatchewan'), ('Canada/Yukon', 'Canada/Yukon'), ('Chile/Continental', 'Chile/Continental'), ('Chile/EasterIsland', 'Chile/EasterIsland'), ('Cuba', 'Cuba'), ('EET', 'EET'), ('EST', 'EST'), ('EST5EDT', 'EST5EDT'), ('Egypt', 'Egypt'), ('Eire', 'Eire'), ('Etc/GMT', 'Etc/GMT'), ('Etc/GMT+0', 'Etc/GMT+0'), ('Etc/GMT+1', 'Etc/GMT+1'), ('Etc/GMT+10', 'Etc/GMT+10'), ('Etc/GMT+11', 'Etc/GMT+11'), ('Etc/GMT+12', 'Etc/GMT+12'), ('Etc/GMT+2', 'Etc/GMT+2'), ('Etc/GMT+3', 'Etc/GMT+3'), ('Etc/GMT+4', 'Etc/GMT+4'), ('Etc/GMT+5', 'Etc/GMT+5'), ('Etc/GMT+6', 'Etc/GMT+6'), ('Etc/GMT+7', 'Etc/GMT+7'), ('Etc/GMT+8', 'Etc/GMT+8'), ('Etc/GMT+9', 'Etc/GMT+9'), ('Etc/GMT-0', 'Etc/GMT-0'), ('Etc/GMT-1', 'Etc/GMT-1'), ('Etc/GMT-10', 'Etc/GMT-10'), ('Etc/GMT-11', 'Etc/GMT-11'), ('Etc/GMT-12', 'Etc/GMT-12'), ('Etc/GMT-13', 'Etc/GMT-13'), ('Etc/GMT-14', 'Etc/GMT-14'), ('Etc/GMT-2', 'Etc/GMT-2'), ('Etc/GMT-3', 'Etc/GMT-3'), ('Etc/GMT-4', 'Etc/GMT-4'), ('Etc/GMT-5', 'Etc/GMT-5'), ('Etc/GMT-6', 'Etc/GMT-6'), ('Etc/GMT-7', 'Etc/GMT-7'), ('Etc/GMT-8', 'Etc/GMT-8'), ('Etc/GMT-9', 'Etc/GMT-9'), ('Etc/GMT0', 'Etc/GMT0'), ('Etc/Greenwich', 'Etc/Greenwich'), ('Etc/UCT', 'Etc/UCT'), ('Etc/UTC', 'Etc/UTC'), ('Etc/Universal', 'Etc/Universal'), ('Etc/Zulu', 'Etc/Zulu'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belfast', 'Europe/Belfast'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Nicosia', 'Europe/Nicosia'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Tiraspol', 'Europe/Tiraspol'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GB', 'GB'), ('GB-Eire', 'GB-Eire'), ('GMT', 'GMT'), ('GMT+0', 'GMT+0'), ('GMT-0', 'GMT-0'), ('GMT0', 'GMT0'), ('Greenwich', 'Greenwich'), ('HST', 'HST'), ('Hongkong', 'Hongkong'), ('Iceland', 'Iceland'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Iran', 'Iran'), ('Israel', 'Israel'), ('Jamaica', 'Jamaica'), ('Japan', 'Japan'), ('Kwajalein', 'Kwajalein'), ('Libya', 'Libya'), ('MET', 'MET'), ('MST', 'MST'), ('MST7MDT', 'MST7MDT'), ('Mexico/BajaNorte', 'Mexico/BajaNorte'), ('Mexico/BajaSur', 'Mexico/BajaSur'), ('Mexico/General', 'Mexico/General'), ('NZ', 'NZ'), ('NZ-CHAT', 'NZ-CHAT'), ('Navajo', 'Navajo'), ('PRC', 'PRC'), ('PST8PDT', 'PST8PDT'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Johnston', 'Pacific/Johnston'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Samoa', 'Pacific/Samoa'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('Pacific/Yap', 'Pacific/Yap'), ('Poland', 'Poland'), ('Portugal', 'Portugal'), ('ROC', 'ROC'), ('ROK', 'ROK'), ('Singapore', 'Singapore'), ('Turkey', 'Turkey'), ('UCT', 'UCT'), ('US/Alaska', 'US/Alaska'), ('US/Aleutian', 'US/Aleutian'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/East-Indiana', 'US/East-Indiana'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Indiana-Starke', 'US/Indiana-Starke'), ('US/Michigan', 'US/Michigan'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('US/Samoa', 'US/Samoa'), ('UTC', 'UTC'), ('Universal', 'Universal'), ('W-SU', 'W-SU'), ('WET', 'WET'), ('Zulu', 'Zulu')], default='UTC', max_length=63)), + ], + options={ + 'verbose_name': 'Настройки системы', + 'verbose_name_plural': 'Настройки системы', + }, + ), + migrations.CreateModel( + name='LocalDatabase', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Имя базы данных')), + ('host', models.CharField(default='localhost', max_length=255, verbose_name='Хост базы данных')), + ('port', models.IntegerField(default=5432, verbose_name='Порт базы данных')), + ('user', models.CharField(max_length=255, verbose_name='Пользователь базы данных')), + ('database', models.CharField(max_length=255, verbose_name='Название базы данных')), + ('password', models.CharField(max_length=255, verbose_name='Пароль базы данных')), + ('is_active', models.BooleanField(default=True, verbose_name='Активна ли база данных')), + ], + options={ + 'verbose_name': 'База данных', + 'verbose_name_plural': 'Базы данных', + }, + ), + migrations.CreateModel( + name='TelegramSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('bot_token', models.CharField(help_text='Токен вашего бота Telegram', max_length=255)), + ('chat_id', models.CharField(help_text='ID чата для отправки сообщений', max_length=255)), + ('username', models.CharField(blank=True, help_text='Имя пользователя для бота', max_length=255, null=True)), + ], + options={ + 'verbose_name': 'Telegram', + 'verbose_name_plural': 'Telegram', + }, + ), + ] diff --git a/hotels/migrations/0001_initial.py b/hotels/migrations/0001_initial.py new file mode 100644 index 00000000..04dfadd7 --- /dev/null +++ b/hotels/migrations/0001_initial.py @@ -0,0 +1,138 @@ +# Generated by Django 5.1.4 on 2024-12-25 04:55 + +import django.core.validators +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='APIConfiguration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Название API')), + ('url', models.URLField(verbose_name='URL API')), + ('token', models.CharField(blank=True, max_length=255, null=True, verbose_name='Токен')), + ('username', models.CharField(blank=True, max_length=255, null=True, verbose_name='Логин')), + ('password', models.CharField(blank=True, max_length=255, null=True, verbose_name='Пароль')), + ('last_updated', models.DateTimeField(auto_now=True, verbose_name='Дата последнего обновления')), + ], + options={ + 'verbose_name': 'Конфигурация API', + 'verbose_name_plural': 'Конфигурации API', + }, + ), + migrations.CreateModel( + name='FraudLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reservation_id', models.BigIntegerField(verbose_name='ID бронирования')), + ('guest_name', models.CharField(blank=True, max_length=255, null=True)), + ('check_in_date', models.DateField(verbose_name='Дата заезда')), + ('detected_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата обнаружения')), + ('message', models.TextField(verbose_name='Сообщение')), + ], + options={ + 'verbose_name': 'Журнал мошенничества', + 'verbose_name_plural': 'Журналы мошенничества', + }, + ), + migrations.CreateModel( + name='Guest', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Имя гостя')), + ('birthdate', models.DateField(blank=True, null=True, verbose_name='Дата рождения')), + ('phone', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator(message='Введите корректный номер телефона (до 15 цифр).', regex='^\\+?1?\\d{9,15}$')], verbose_name='Телефон')), + ('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')), + ], + options={ + 'verbose_name': 'Гость', + 'verbose_name_plural': 'Гости', + }, + ), + migrations.CreateModel( + name='Hotel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Название отеля')), + ('hotel_id', models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name='ID отеля')), + ('external_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='Внешний PMS ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создан')), + ('phone', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator(message='Введите корректный номер телефона (до 15 цифр).', regex='^\\+?1?\\d{9,15}$')], verbose_name='Телефон')), + ('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')), + ('address', models.CharField(blank=True, max_length=255, null=True, verbose_name='Адрес')), + ('city', models.CharField(blank=True, max_length=255, null=True, verbose_name='Город')), + ('timezone', models.CharField(choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Timbuktu', 'Africa/Timbuktu'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/ComodRivadavia', 'America/Argentina/ComodRivadavia'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Atka', 'America/Atka'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Ensenada', 'America/Ensenada'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fort_Wayne', 'America/Fort_Wayne'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Knox_IN', 'America/Knox_IN'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montreal', 'America/Montreal'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Acre', 'America/Porto_Acre'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Rosario', 'America/Rosario'), ('America/Santa_Isabel', 'America/Santa_Isabel'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Shiprock', 'America/Shiprock'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Virgin', 'America/Virgin'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/South_Pole', 'Antarctica/South_Pole'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Ashkhabad', 'Asia/Ashkhabad'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Chongqing', 'Asia/Chongqing'), ('Asia/Chungking', 'Asia/Chungking'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Dacca', 'Asia/Dacca'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Harbin', 'Asia/Harbin'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Istanbul', 'Asia/Istanbul'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kashgar', 'Asia/Kashgar'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macao', 'Asia/Macao'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Tel_Aviv', 'Asia/Tel_Aviv'), ('Asia/Thimbu', 'Asia/Thimbu'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ujung_Pandang', 'Asia/Ujung_Pandang'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Ulan_Bator', 'Asia/Ulan_Bator'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Jan_Mayen', 'Atlantic/Jan_Mayen'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/ACT', 'Australia/ACT'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Canberra', 'Australia/Canberra'), ('Australia/Currie', 'Australia/Currie'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/LHI', 'Australia/LHI'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/NSW', 'Australia/NSW'), ('Australia/North', 'Australia/North'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Queensland', 'Australia/Queensland'), ('Australia/South', 'Australia/South'), ('Australia/Sydney', 'Australia/Sydney'), ('Australia/Tasmania', 'Australia/Tasmania'), ('Australia/Victoria', 'Australia/Victoria'), ('Australia/West', 'Australia/West'), ('Australia/Yancowinna', 'Australia/Yancowinna'), ('Brazil/Acre', 'Brazil/Acre'), ('Brazil/DeNoronha', 'Brazil/DeNoronha'), ('Brazil/East', 'Brazil/East'), ('Brazil/West', 'Brazil/West'), ('CET', 'CET'), ('CST6CDT', 'CST6CDT'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Canada/Saskatchewan', 'Canada/Saskatchewan'), ('Canada/Yukon', 'Canada/Yukon'), ('Chile/Continental', 'Chile/Continental'), ('Chile/EasterIsland', 'Chile/EasterIsland'), ('Cuba', 'Cuba'), ('EET', 'EET'), ('EST', 'EST'), ('EST5EDT', 'EST5EDT'), ('Egypt', 'Egypt'), ('Eire', 'Eire'), ('Etc/GMT', 'Etc/GMT'), ('Etc/GMT+0', 'Etc/GMT+0'), ('Etc/GMT+1', 'Etc/GMT+1'), ('Etc/GMT+10', 'Etc/GMT+10'), ('Etc/GMT+11', 'Etc/GMT+11'), ('Etc/GMT+12', 'Etc/GMT+12'), ('Etc/GMT+2', 'Etc/GMT+2'), ('Etc/GMT+3', 'Etc/GMT+3'), ('Etc/GMT+4', 'Etc/GMT+4'), ('Etc/GMT+5', 'Etc/GMT+5'), ('Etc/GMT+6', 'Etc/GMT+6'), ('Etc/GMT+7', 'Etc/GMT+7'), ('Etc/GMT+8', 'Etc/GMT+8'), ('Etc/GMT+9', 'Etc/GMT+9'), ('Etc/GMT-0', 'Etc/GMT-0'), ('Etc/GMT-1', 'Etc/GMT-1'), ('Etc/GMT-10', 'Etc/GMT-10'), ('Etc/GMT-11', 'Etc/GMT-11'), ('Etc/GMT-12', 'Etc/GMT-12'), ('Etc/GMT-13', 'Etc/GMT-13'), ('Etc/GMT-14', 'Etc/GMT-14'), ('Etc/GMT-2', 'Etc/GMT-2'), ('Etc/GMT-3', 'Etc/GMT-3'), ('Etc/GMT-4', 'Etc/GMT-4'), ('Etc/GMT-5', 'Etc/GMT-5'), ('Etc/GMT-6', 'Etc/GMT-6'), ('Etc/GMT-7', 'Etc/GMT-7'), ('Etc/GMT-8', 'Etc/GMT-8'), ('Etc/GMT-9', 'Etc/GMT-9'), ('Etc/GMT0', 'Etc/GMT0'), ('Etc/Greenwich', 'Etc/Greenwich'), ('Etc/UCT', 'Etc/UCT'), ('Etc/UTC', 'Etc/UTC'), ('Etc/Universal', 'Etc/Universal'), ('Etc/Zulu', 'Etc/Zulu'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belfast', 'Europe/Belfast'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Nicosia', 'Europe/Nicosia'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Tiraspol', 'Europe/Tiraspol'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GB', 'GB'), ('GB-Eire', 'GB-Eire'), ('GMT', 'GMT'), ('GMT+0', 'GMT+0'), ('GMT-0', 'GMT-0'), ('GMT0', 'GMT0'), ('Greenwich', 'Greenwich'), ('HST', 'HST'), ('Hongkong', 'Hongkong'), ('Iceland', 'Iceland'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Iran', 'Iran'), ('Israel', 'Israel'), ('Jamaica', 'Jamaica'), ('Japan', 'Japan'), ('Kwajalein', 'Kwajalein'), ('Libya', 'Libya'), ('MET', 'MET'), ('MST', 'MST'), ('MST7MDT', 'MST7MDT'), ('Mexico/BajaNorte', 'Mexico/BajaNorte'), ('Mexico/BajaSur', 'Mexico/BajaSur'), ('Mexico/General', 'Mexico/General'), ('NZ', 'NZ'), ('NZ-CHAT', 'NZ-CHAT'), ('Navajo', 'Navajo'), ('PRC', 'PRC'), ('PST8PDT', 'PST8PDT'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Johnston', 'Pacific/Johnston'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Samoa', 'Pacific/Samoa'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('Pacific/Yap', 'Pacific/Yap'), ('Poland', 'Poland'), ('Portugal', 'Portugal'), ('ROC', 'ROC'), ('ROK', 'ROK'), ('Singapore', 'Singapore'), ('Turkey', 'Turkey'), ('UCT', 'UCT'), ('US/Alaska', 'US/Alaska'), ('US/Aleutian', 'US/Aleutian'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/East-Indiana', 'US/East-Indiana'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Indiana-Starke', 'US/Indiana-Starke'), ('US/Michigan', 'US/Michigan'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('US/Samoa', 'US/Samoa'), ('UTC', 'UTC'), ('Universal', 'Universal'), ('W-SU', 'W-SU'), ('WET', 'WET'), ('Zulu', 'Zulu')], default='UTC', max_length=63, verbose_name='Часовой пояс')), + ('description', models.TextField(blank=True, null=True, verbose_name='Описание')), + ], + options={ + 'verbose_name': 'Отель', + 'verbose_name_plural': 'Отели', + }, + ), + migrations.CreateModel( + name='Reservation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reservation_id', models.BigIntegerField(unique=True, verbose_name='ID бронирования')), + ('room_number', models.CharField(blank=True, max_length=255, null=True, verbose_name='Номер комнаты')), + ('room_type', models.CharField(max_length=255, verbose_name='Тип комнаты')), + ('check_in', models.DateTimeField(verbose_name='Дата заезда')), + ('check_out', models.DateTimeField(verbose_name='Дата выезда')), + ('status', models.CharField(max_length=50, verbose_name='Статус')), + ('price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Цена')), + ('discount', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Скидка')), + ], + options={ + 'verbose_name': 'Бронирование', + 'verbose_name_plural': 'Бронирования', + }, + ), + migrations.CreateModel( + name='Room', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('number', models.CharField(max_length=50, unique=True, verbose_name='Номер комнаты')), + ('external_id', models.CharField(max_length=255, unique=True, verbose_name='Внешний ID комнаты')), + ('description', models.TextField(blank=True, null=True, verbose_name='Описание')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')), + ], + options={ + 'verbose_name': 'Номер', + 'verbose_name_plural': 'Номера', + }, + ), + migrations.CreateModel( + name='UserHotel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'verbose_name': 'Пользователь отеля', + 'verbose_name_plural': 'Пользователи отелей', + }, + ), + migrations.CreateModel( + name='APIRequestLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('request_time', models.DateTimeField(auto_now_add=True, verbose_name='Время запроса')), + ('response_status', models.IntegerField(validators=[django.core.validators.MinValueValidator(100), django.core.validators.MaxValueValidator(599)], verbose_name='HTTP статус ответа')), + ('response_data', models.JSONField(blank=True, null=True, verbose_name='Данные ответа')), + ('api', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.apiconfiguration', verbose_name='API')), + ], + options={ + 'verbose_name': 'Журнал запросов API', + 'verbose_name_plural': 'Журналы запросов API', + }, + ), + ] diff --git a/hotels/migrations/0002_initial.py b/hotels/migrations/0002_initial.py new file mode 100644 index 00000000..c17b5c88 --- /dev/null +++ b/hotels/migrations/0002_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 5.1.4 on 2024-12-25 04:55 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('hotels', '0001_initial'), + ('pms_integration', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='hotel', + name='pms', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pms_integration.pmsconfiguration', verbose_name='PMS система'), + ), + migrations.AddField( + model_name='fraudlog', + name='hotel', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frauds', to='hotels.hotel'), + ), + migrations.AddField( + model_name='reservation', + name='hotel', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'), + ), + migrations.AddField( + model_name='guest', + name='reservation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guests', to='hotels.reservation', verbose_name='Бронирование'), + ), + migrations.AddField( + model_name='room', + name='hotel', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to='hotels.hotel', verbose_name='Отель'), + ), + migrations.AddField( + model_name='userhotel', + name='hotel', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hotel_users', to='hotels.hotel', verbose_name='Отель'), + ), + ] diff --git a/hotels/migrations/0003_rename_external_id_hotel_external_id_pms_and_more.py b/hotels/migrations/0003_rename_external_id_hotel_external_id_pms_and_more.py new file mode 100644 index 00000000..2d58c1a1 --- /dev/null +++ b/hotels/migrations/0003_rename_external_id_hotel_external_id_pms_and_more.py @@ -0,0 +1,54 @@ +# Generated by Django 5.1.4 on 2024-12-25 05:10 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hotels', '0002_initial'), + ('users', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='hotel', + old_name='external_id', + new_name='external_id_pms', + ), + migrations.AddField( + model_name='userhotel', + name='user', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='user_hotels', to='users.user', verbose_name='Пользователь'), + preserve_default=False, + ), + migrations.AddIndex( + model_name='apirequestlog', + index=models.Index(fields=['api'], name='hotels_apir_api_id_686bb0_idx'), + ), + migrations.AddIndex( + model_name='apirequestlog', + index=models.Index(fields=['request_time'], name='hotels_apir_request_f65147_idx'), + ), + migrations.AddIndex( + model_name='fraudlog', + index=models.Index(fields=['reservation_id'], name='hotels_frau_reserva_5a26b7_idx'), + ), + migrations.AddIndex( + model_name='fraudlog', + index=models.Index(fields=['detected_at'], name='hotels_frau_detecte_07e626_idx'), + ), + migrations.AddIndex( + model_name='reservation', + index=models.Index(fields=['hotel', 'check_in', 'check_out'], name='hotels_rese_hotel_i_6c527e_idx'), + ), + migrations.AddIndex( + model_name='room', + index=models.Index(fields=['hotel', 'number'], name='hotels_room_hotel_i_a7c4fc_idx'), + ), + migrations.AddConstraint( + model_name='room', + constraint=models.UniqueConstraint(fields=('hotel', 'number'), name='unique_hotel_room'), + ), + ] diff --git a/pms_integration/migrations/0001_initial.py b/pms_integration/migrations/0001_initial.py new file mode 100644 index 00000000..01a02535 --- /dev/null +++ b/pms_integration/migrations/0001_initial.py @@ -0,0 +1,50 @@ +# Generated by Django 5.1.4 on 2024-12-25 04:55 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('hotels', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='PMSConfiguration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Название PMS')), + ('url', models.URLField(verbose_name='URL API')), + ('token', models.CharField(blank=True, max_length=255, null=True, verbose_name='Токен')), + ('public_key', models.CharField(blank=True, max_length=255, null=True, verbose_name='Публичный ключ')), + ('private_key', models.CharField(blank=True, max_length=255, null=True, verbose_name='Приватный ключ')), + ('username', models.CharField(blank=True, max_length=255, null=True, verbose_name='Логин')), + ('password', models.CharField(blank=True, max_length=255, null=True, verbose_name='Пароль')), + ('plugin_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Плагин')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')), + ], + options={ + 'verbose_name': 'PMS система', + 'verbose_name_plural': 'PMS системы', + }, + ), + migrations.CreateModel( + name='PMSIntegrationLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('checked_at', models.DateTimeField(auto_now_add=True, verbose_name='Время проверки')), + ('status', models.CharField(choices=[('success', 'Успех'), ('error', 'Ошибка')], max_length=50, verbose_name='Статус')), + ('message', models.TextField(blank=True, null=True, verbose_name='Сообщение')), + ('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')), + ], + options={ + 'verbose_name': 'Журнал интеграции PMS', + 'verbose_name_plural': 'Журналы интеграции PMS', + 'indexes': [models.Index(fields=['hotel'], name='pms_integra_hotel_i_ade4da_idx'), models.Index(fields=['checked_at'], name='pms_integra_checked_938acc_idx'), models.Index(fields=['status'], name='pms_integra_status_358b64_idx')], + }, + ), + ] diff --git a/pms_integration/migrations/0002_pmsconfiguration_hotels.py b/pms_integration/migrations/0002_pmsconfiguration_hotels.py new file mode 100644 index 00000000..cd1b2827 --- /dev/null +++ b/pms_integration/migrations/0002_pmsconfiguration_hotels.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2024-12-25 06:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hotels', '0003_rename_external_id_hotel_external_id_pms_and_more'), + ('pms_integration', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='pmsconfiguration', + name='hotels', + field=models.ManyToManyField(related_name='pms_configurations', to='hotels.hotel', verbose_name='Отели'), + ), + ] diff --git a/pms_integration/plugins/travelline_pms.py b/pms_integration/plugins/travelline_pms.py new file mode 100644 index 00000000..d9252168 --- /dev/null +++ b/pms_integration/plugins/travelline_pms.py @@ -0,0 +1,116 @@ +import logging +import requests +from datetime import datetime +from .base_plugin import BasePMSPlugin + +class TravelLinePMSPlugin(BasePMSPlugin): + """ + Плагин для интеграции с PMS TravelLine. + """ + BASE_URL = "https://partner.tlintegration.com/api/webpms/v1" + + def __init__(self, pms_config): + """ + Инициализация плагина с конфигурацией PMS. + + :param pms_config: Конфигурация PMS (объект PMSConfiguration). + """ + super().__init__(pms_config) + self.api_key = pms_config.token + self.logger = logging.getLogger(self.__class__.__name__) + + def _get_headers(self): + """ + Возвращает заголовки для запросов. + """ + return { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + + def _fetch_data(self): + """ + Получение данных из API TravelLine (поиск бронирований). + + :return: Список номеров бронирований. + """ + url = f"{self.BASE_URL}/bookings" + params = { + "roomId": self.pms_config.room_id, + "modifiedFrom": self.pms_config.modified_from, + "modifiedTo": self.pms_config.modified_to, + "state": self.pms_config.state, + "affectsPeriodFrom": self.pms_config.affects_period_from, + "affectsPeriodTo": self.pms_config.affects_period_to, + } + params = {k: v for k, v in params.items() if v is not None} + + try: + response = requests.get(url, headers=self._get_headers(), params=params) + response.raise_for_status() + self.logger.info("Данные успешно получены из API TravelLine.") + return response.json().get("bookingNumbers", []) + except requests.RequestException as e: + self.logger.error(f"Ошибка при запросе к API TravelLine: {e}") + return [] + + def fetch_data(self): + """ + Обертка для получения данных из API TravelLine с дополнительной обработкой. + + :return: Список номеров бронирований. + """ + return self._fetch_data() + + def get_default_parser_settings(self): + """ + Возвращает настройки парсера по умолчанию. + """ + return { + "field_mapping": { + "reservation_id": "bookingNumber", + "check_in": "actualCheckInDateTime", + "check_out": "actualCheckOutDateTime", + "room_number": "roomId", + "status": "state", + }, + "date_format": "%Y-%m-%dT%H:%M" + } + + def process_data(self, booking_number, room_stay_id, action, actual_date_time): + """ + Обработка данных для заселения или выселения проживания. + + :param booking_number: Номер бронирования. + :param room_stay_id: Идентификатор проживания. + :param action: Действие ("check-in" или "check-out"). + :param actual_date_time: Фактические дата и время. + :return: Ответ API. + """ + if action not in ["check-in", "check-out"]: + raise ValueError("Invalid action. Must be 'check-in' or 'check-out'.") + + url = f"{self.BASE_URL}/bookings/{booking_number}/room-stays/{room_stay_id}/{action}" + payload = { + f"actual{action.capitalize()}DateTime": actual_date_time + } + + try: + response = requests.post(url, headers=self._get_headers(), json=payload) + response.raise_for_status() + self.logger.info(f"Успешно выполнено действие '{action}' для бронирования {booking_number}.") + return response.json() + except requests.RequestException as e: + self.logger.error(f"Ошибка при выполнении действия '{action}': {e}") + return {} + + def validate_plugin(self): + """ + Проверка плагина на соответствие требованиям. + """ + required_methods = ["fetch_data", "get_default_parser_settings", "_fetch_data"] + for method in required_methods: + if not hasattr(self, method): + raise ValueError(f"Плагин {type(self).__name__} не реализует метод {method}.") + self.logger.info(f"Плагин {self.__class__.__name__} успешно прошел валидацию.") + return True diff --git a/scheduler/migrations/0001_initial.py b/scheduler/migrations/0001_initial.py new file mode 100644 index 00000000..6216b61e --- /dev/null +++ b/scheduler/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.4 on 2024-12-25 04:55 + +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/users/migrations/0001_initial.py b/users/migrations/0001_initial.py new file mode 100644 index 00000000..c01fa758 --- /dev/null +++ b/users/migrations/0001_initial.py @@ -0,0 +1,63 @@ +# Generated by Django 5.1.4 on 2024-12-25 04:55 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('telegram_id', models.BigIntegerField(blank=True, null=True, unique=True, verbose_name='ID Телеграм')), + ('chat_id', models.BigIntegerField(blank=True, null=True, unique=True, verbose_name='ID чата в телеграм')), + ('role', models.CharField(choices=[('admin', 'Администратор системы'), ('hotel_user', 'Сотрудник отеля')], default='hotel_user', max_length=20, verbose_name='Роль')), + ('confirmed', models.BooleanField(default=False, verbose_name='Подтвержден')), + ('groups', models.ManyToManyField(blank=True, related_name='custom_user_set', to='auth.group')), + ('user_permissions', models.ManyToManyField(blank=True, related_name='custom_user_set', to='auth.permission')), + ], + options={ + 'verbose_name': 'Пользователь', + 'verbose_name_plural': 'Пользователи', + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='NotificationSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('telegram_enabled', models.BooleanField(default=True, verbose_name='Уведомления в Telegram')), + ('email_enabled', models.BooleanField(default=False, verbose_name='Уведомления по Email')), + ('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email для уведомлений')), + ('notification_time', models.TimeField(default='09:00', verbose_name='Время отправки уведомлений')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='Пользователь')), + ], + options={ + 'verbose_name': 'Способ оповещения', + 'verbose_name_plural': 'Способы оповещений', + }, + ), + ] From 9bff204fb4f81da8b9349912b159049171f9e3a6 Mon Sep 17 00:00:00 2001 From: trevor Date: Fri, 27 Dec 2024 09:54:58 +0900 Subject: [PATCH 3/3] new --- bot/operations/hotels.py | 2 +- pms_integration/manager.py | 14 +- pms_integration/models.py | 5 - pms_integration/plugins/bnovo_pms.py | 246 ++++++++++++++++----------- pms_integration/plugins/ecvi_pms.py | 2 +- 5 files changed, 156 insertions(+), 113 deletions(-) diff --git a/bot/operations/hotels.py b/bot/operations/hotels.py index 21c8f0cd..5598ea09 100644 --- a/bot/operations/hotels.py +++ b/bot/operations/hotels.py @@ -151,7 +151,7 @@ async def check_pms(update, context): if hasattr(pms_manager.plugin, 'fetch_data') and callable(pms_manager.plugin.fetch_data): # Плагин поддерживает метод fetch_data report = await pms_manager.plugin._fetch_data() - logger.debug(f"REPORT: {report}, TYPE: {type(report)}") + logger.debug(f"TYPE: {type(report)}") else: await query.edit_message_text("Подходящий способ интеграции с PMS не найден.") return diff --git a/pms_integration/manager.py b/pms_integration/manager.py index b1251aab..d4052093 100644 --- a/pms_integration/manager.py +++ b/pms_integration/manager.py @@ -51,12 +51,18 @@ class PMSIntegrationManager: def load_plugin(self): """ - Загружает плагин для PMS на основе конфигурации. + Загружает плагин для PMS на основе конфигурации отеля. """ plugins = PluginLoader.load_plugins() - if self.pms_config.plugin_name not in plugins: - raise ValueError(f"Плагин для PMS {self.pms_config.plugin_name} не найден.") - self.plugin = plugins[self.pms_config.plugin_name](self.pms_config) + if not self.hotel: + raise ValueError("Отель не загружен. Пожалуйста, вызовите load_hotel перед загрузкой плагина.") + if not self.hotel.pms: + raise ValueError(f"Отель {self.hotel.name} не связан с PMS конфигурацией.") + if self.hotel.pms.plugin_name not in plugins: + raise ValueError(f"Плагин для PMS {self.hotel.pms.plugin_name} не найден.") + + # Передача объекта Hotel в плагин + self.plugin = plugins[self.hotel.pms.plugin_name](self.hotel) def fetch_data(self): """ diff --git a/pms_integration/models.py b/pms_integration/models.py index 8f37b9db..acf7bcee 100644 --- a/pms_integration/models.py +++ b/pms_integration/models.py @@ -14,11 +14,6 @@ class PMSConfiguration(models.Model): password = models.CharField(max_length=255, blank=True, null=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="Дата создания") - hotels = models.ManyToManyField( - 'hotels.Hotel', - related_name='pms_configurations', - verbose_name="Отели" - ) def __str__(self): return self.name diff --git a/pms_integration/plugins/bnovo_pms.py b/pms_integration/plugins/bnovo_pms.py index dc5fc251..e624b32a 100644 --- a/pms_integration/plugins/bnovo_pms.py +++ b/pms_integration/plugins/bnovo_pms.py @@ -4,20 +4,51 @@ from datetime import datetime, timedelta from asgiref.sync import sync_to_async from .base_plugin import BasePMSPlugin from pms_integration.models import PMSConfiguration +from hotels.models import Reservation, Hotel from touchh.utils.log import CustomLogger -from pms_integration.models import PMSConfiguration -from hotels.models import Hotel, Reservation +import logging -logger = CustomLogger(name="BnovoPMS Plugin", log_level="DEBUG").get_logger() +import logging + +# Настройка логирования +logging.basicConfig( + level=logging.WARNING, # Установите уровень логов для всех обработчиков + format='%(asctime)s [%(levelname)s] %(message)s', + handlers=[ + logging.FileHandler("bnovo_plugin.log"), # Логи пишутся в файл + logging.StreamHandler() # Логи выводятся в консоль + ] +) + +# Создаем отдельный логгер для консоли с уровнем INFO +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.WARNING) # Меняем уровень логов для консоли +console_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) + +# Основной логгер +logger = logging.getLogger("BnovoPMS Plugin") +logger.addHandler(console_handler) +logger.setLevel(logging.WARNING) # Основной уровень логов class BnovoPMSPlugin(BasePMSPlugin): """Плагин для работы с PMS Bnovo.""" - def __init__(self, config): - super().__init__(config) - self.api_url = config.url.rstrip("/") - self.username = config.username - self.password = config.password + def __init__(self, hotel): + super().__init__(hotel) + + if not isinstance(hotel, Hotel): + logger.error("Ожидался объект Hotel, но получен другой тип.") + raise ValueError("Для инициализации плагина требуется объект Hotel.") + + self.hotel = hotel + self.pms_config = hotel.pms # Связь отеля с PMSConfiguration + if not self.pms_config: + logger.error(f"Отель {hotel.id} не связан с конфигурацией PMS.") + raise ValueError(f"Отель {hotel.id} не связан с конфигурацией PMS.") + + self.api_url = self.pms_config.url.rstrip("/") + self.username = self.pms_config.username + self.password = self.pms_config.password self.token = None if not self.api_url: @@ -35,27 +66,26 @@ class BnovoPMSPlugin(BasePMSPlugin): "timezone": "UTC" } - async def _get_stored_token(self, hotel_id): - """Получение токена из базы данных для конкретного отеля.""" + async def _get_stored_token(self): + """Получение токена из конфигурации PMS отеля.""" try: - logger.debug(f"Попытка получения токена из базы данных для отеля: {hotel_id}.") - config = await sync_to_async(PMSConfiguration.objects.get)(plugin_name="bnovo", hotel_id=hotel_id) - logger.debug(f"Токен из базы данных: {config.token}") - return config.token - except PMSConfiguration.DoesNotExist: - logger.warning(f"Токен отсутствует в базе данных для отеля: {hotel_id}.") + logger.debug(f"Попытка получения токена для отеля {self.hotel.id}.") + token = self.pms_config.token + logger.debug(f"Токен из базы данных: {token}") + return token + except Exception as e: + logger.warning(f"Ошибка при получении токена для отеля {self.hotel.id}: {e}") return None - async def _save_token_to_db(self, sid, hotel_id): - """Сохраняет токен (SID) в базу данных для конкретного отеля.""" + async def _save_token_to_db(self, sid): + """Сохраняет токен (SID) в конфигурации PMS отеля.""" try: - logger.debug(f"Сохранение токена в базу данных для отеля {hotel_id}: {sid}") - await sync_to_async(PMSConfiguration.objects.update_or_create)( - plugin_name="bnovo", hotel_id=hotel_id, defaults={"token": sid} - ) + logger.debug(f"Сохранение токена для отеля {self.hotel.id}: {sid}") + self.pms_config.token = sid + await sync_to_async(self.pms_config.save)() logger.debug("Токен успешно сохранен.") except Exception as e: - logger.error(f"Ошибка сохранения токена в базу данных для отеля {hotel_id}: {e}") + logger.error(f"Ошибка сохранения токена для отеля {self.hotel.id}: {e}") def _get_auth_headers(self): """Создает заголовки авторизации.""" @@ -69,8 +99,8 @@ class BnovoPMSPlugin(BasePMSPlugin): logger.debug(f"Добавлен токен в заголовки: {self.token}") return headers - async def _fetch_session(self, hotel_id): - """Получение нового токена (SID) через запрос для конкретного отеля.""" + async def _fetch_session(self): + """Получение нового токена (SID) через запрос.""" url = f"{self.api_url}/" payload = {"username": self.username, "password": self.password} headers = self._get_auth_headers() @@ -85,7 +115,7 @@ class BnovoPMSPlugin(BasePMSPlugin): if sid: self.token = sid logger.debug(f"Получен новый SID: {sid}") - await self._save_token_to_db(sid, hotel_id) + await self._save_token_to_db(sid) else: logger.error("Не удалось извлечь SID из ответа.") raise ValueError("Не удалось извлечь SID из ответа.") @@ -93,14 +123,14 @@ class BnovoPMSPlugin(BasePMSPlugin): logger.error(f"Ошибка авторизации: {response.status_code}, {response.text}") raise ValueError(f"Ошибка авторизации: {response.status_code}, {response.text}") - async def _fetch_account_data(self, hotel_id): - """Получение данных аккаунта через эндпоинт /account/current для конкретного отеля.""" - logger.info(f"Начало получения данных аккаунта для отеля: {hotel_id}.") - self.token = await self._get_stored_token(hotel_id) + async def _fetch_account_data(self): + """Получение данных аккаунта через эндпоинт /account/current.""" + logger.info(f"Начало получения данных аккаунта для отеля {self.hotel.id}.") + self.token = await self._get_stored_token() if not self.token: logger.info("Токен отсутствует, выполняем авторизацию.") - await self._fetch_session(hotel_id) + await self._fetch_session() url = f"{self.api_url}/account/current" headers = self._get_auth_headers() @@ -114,51 +144,49 @@ class BnovoPMSPlugin(BasePMSPlugin): try: account_data = response.json() - logger.debug(f"Полученные данные аккаунта: {json.dumps(account_data, indent=2)}") + logger.debug(f"Полученные данные аккаунта:") except json.JSONDecodeError as e: logger.error(f"Ошибка декодирования JSON: {e}") raise ValueError(f"Ошибка декодирования JSON: {e}") return account_data - async def fetch_and_log_account_data(self, hotel_id): + async def _fetch_and_log_account_data(self): """Вызов метода _fetch_account_data и вывод результата в лог.""" - logger.info(f"Запуск получения и логирования данных аккаунта для отеля: {hotel_id}.") + logger.info(f"Запуск получения и логирования данных аккаунта для отеля {self.hotel.id}.") try: - account_data = await self._fetch_account_data(hotel_id) - logger.info(f"Успешно полученные данные аккаунта: {json.dumps(account_data, indent=2)}") + account_data = await self._fetch_account_data() + logger.info(f"Успешно полученные данные аккаунта:") return account_data except Exception as e: logger.error(f"Ошибка при получении данных аккаунта: {e}") raise - async def fetch_data_with_account_info(self, hotel_id): - """Получение данных аккаунта и бронирований для конкретного отеля.""" - logger.info(f"Запуск процесса получения данных аккаунта и бронирований для отеля: {hotel_id}.") + async def _fetch_data_with_account_info(self): + """Получение данных аккаунта и бронирований.""" + logger.info(f"Запуск процесса получения данных аккаунта и бронирований для отеля {self.hotel.id}.") try: - account_data = await self.fetch_and_log_account_data(hotel_id) + account_data = await self.fetch_and_log_account_data() logger.info("Данные аккаунта успешно получены, продолжение с бронированиями.") await self.__fetch_data() except Exception as e: logger.error(f"Ошибка при выполнении полной операции: {e}") - + async def _fetch_data(self): """Получение данных о бронированиях с помощью эндпоинта /dashboard.""" - # Проверяем наличие токена в базе данных - logger.info("Начало процесса получения данных.") - self.token = await self._get_stored_token() + logger.info("Начало процесса получения данных о бронированиях.") - if not self.token: - logger.info("Токен отсутствует, выполняем авторизацию.") - await self._fetch_session() + # # Вызов функции получения данных аккаунта + # try: + # account_data = await self._fetch_and_log_account_data() + # logger.info(f"Данные аккаунта успешно получены:") + # except Exception as e: + # logger.error(f"Ошибка получения данных аккаунта: {e}") + # raise - - accounts = await self._fetch_account_data() - print(f'\n------\nACCOUNTS: {accounts}\n-------\n') - url = f"{self.api_url}/dashboard" now = datetime.now() - create_from = (now - timedelta(days=90)).strftime("%d.%m.%Y") + create_from = (now - timedelta(days=1)).strftime("%d.%m.%Y") create_to = now.strftime("%d.%m.%Y") params = { @@ -171,69 +199,83 @@ class BnovoPMSPlugin(BasePMSPlugin): } headers = self._get_auth_headers() - logger.debug(f"Начальный запрос к {url} с параметрами: {json.dumps(params, indent=2)}") all_bookings = [] while True: - logger.debug(f"Выполнение запроса к {url}") - response = requests.get(url, headers=headers, params=params) - - logger.debug(f"Ответ запроса: статус {response.status_code}, тело {response.text}") - if response.status_code != 200: - logger.error(f"Ошибка при запросе: {response.status_code}, {response.text}") - raise ValueError("Ошибка запроса к /dashboard") - + logger.debug(f"Запрос к {url} с параметрами: {json.dumps(params, indent=2)}") try: + response = requests.get(url, headers=headers, params=params, allow_redirects=False) + + if response.status_code == 302: + logger.warning("Получен код 302. Перенаправление.") + redirected_url = response.headers.get("Location") + if redirected_url: + logger.debug(f"Перенаправление на {redirected_url}") + url = redirected_url + continue + else: + logger.error("Ответ с кодом 302 не содержит заголовка Location.") + raise ValueError("Перенаправление без указанного URL.") + + if response.status_code != 200: + logger.error(f"Ошибка при запросе: {response.status_code}, {response.text}") + raise ValueError("Ошибка запроса к /dashboard") + data = response.json() - logger.debug(f"Полученные данные: {json.dumps(data, indent=2)}") + bookings = data.get("bookings", []) + rooms = data.get("rooms", []) + + + logger.debug(f'bookings: {bookings}\n rooms: {rooms}') + all_bookings.extend(bookings) + + logger.info(f"Получено бронирований: {len(bookings)}. Всего: {len(all_bookings)}.") + + pages_info = data.get("pages", {}) + current_page = pages_info.get("current_page", 1) + total_pages = pages_info.get("total_pages", 1) + + logger.debug(f"Информация о страницах: текущая {current_page}, всего {total_pages}") + if current_page >= total_pages: + break + + params["page"] += 1 + except json.JSONDecodeError as e: - logger.error(f"Ошибка декодирования JSON: {e}") + logger.error(f"Ошибка декодирования JSON: {e}. Ответ: {response.text}") raise ValueError(f"Ошибка декодирования JSON: {e}") - bookings = data.get("bookings", []) - all_bookings.extend(bookings) - - logger.info(f"Получено бронирований за запрос: {len(bookings)}. Всего: {len(all_bookings)}.") - - # Проверяем наличие следующей страницы - pages_info = data.get("pages", {}) - current_page = pages_info.get("current_page", 1) - total_pages = pages_info.get("total_pages", 1) - - logger.debug(f"Информация о страницах: текущая {current_page}, всего {total_pages}") - if current_page >= total_pages: - break - - params["page"] += 1 + except Exception as e: + logger.error(f"Неизвестная ошибка при обработке запроса: {e}") + raise # Сопоставляем бронирования с существующими записями for booking in all_bookings: - booking_id = booking.get("id") - hotel_id = booking.get("hotel_id") - if not booking_id or not hotel_id: - logger.warning("У бронирования отсутствует id или hotel_id. Пропуск.") - continue + try: + booking_id = booking.get("id") + hotel_id = booking.get("hotel_id") - if hotel_id != str(self.config.hotel.external_id_pms): - logger.debug(f"Бронирование {booking_id} не относится к отелю {self.config.hotel.external_id_pms}. Пропуск.") - continue + if hotel_id != str(self.hotel.external_id_pms): + logger.debug(f"Бронирование {booking_id} не относится к отелю {self.hotel.external_id_pms}. Пропуск.") + continue - reservation, created = await sync_to_async(Reservation.objects.update_or_create)( - external_id=booking_id, - defaults={ - "hotel": self.config.hotel, - "status": booking.get("status_name"), - "create_date": booking.get("create_date"), - "arrival": booking.get("arrival"), - "departure": booking.get("departure"), - "room_type": booking.get("initial_room_type_name"), - "data": booking - } - ) + reservation, created = await sync_to_async(Reservation.objects.update_or_create)( + external_id=booking_id, + defaults = { + "hotel": self.hotel, # Объект модели Hotel + "status": booking.get("status_name"), # Статус бронирования + "room_number": booking.get("current_room"), # Номер комнаты (исправлено с create_date) + "check_in": booking.get("arrival"), # Дата заезда + "check_out": booking.get("departure"), # Дата выезда + "room_type": booking.get("initial_room_type_name") # Тип комнаты + } + ) + if created: + logger.info(f"Создана новая запись бронирования: {reservation}") + else: + logger.info(f"Обновлено существующее бронирование: {reservation}") - if created: - logger.info(f"Создана новая запись бронирования: {reservation}") - else: - logger.info(f"Обновлено существующее бронирование: {reservation}") + except Exception as e: + logger.error(f"Ошибка обработки бронирования {booking.get('id')}: {e}") logger.info(f"Все бронирования получены и обработаны. Итоговое количество: {len(all_bookings)}") return all_bookings diff --git a/pms_integration/plugins/ecvi_pms.py b/pms_integration/plugins/ecvi_pms.py index 7e7fff48..73df78df 100644 --- a/pms_integration/plugins/ecvi_pms.py +++ b/pms_integration/plugins/ecvi_pms.py @@ -12,7 +12,7 @@ class EcviPMSPlugin(BasePMSPlugin): def __init__(self, pms_config): super().__init__(pms_config) - + # Инициализация логгера self.logger = logging.getLogger(self.__class__.__name__) # Логгер с именем класса handler_console = logging.StreamHandler() # Потоковый обработчик для вывода в консоль