feat(integrations): добавлена заготовка интеграции Recommerce
- Создана структура marketplaces/ для маркетплейсов - Модели: MarketplaceIntegration, WooCommerceIntegration, RecommerceIntegration - Сервисы: MarketplaceService, WooCommerceService, RecommerceService - RecommerceService содержит методы для работы с API: - test_connection(), sync(), fetch_products() - push_product(), update_stock(), update_price() - IntegrationConfig обновлён с новой интеграцией Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
9
myproject/integrations/services/marketplaces/__init__.py
Normal file
9
myproject/integrations/services/marketplaces/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .base import MarketplaceService
|
||||
from .woocommerce import WooCommerceService
|
||||
from .recommerce import RecommerceService
|
||||
|
||||
__all__ = [
|
||||
'MarketplaceService',
|
||||
'WooCommerceService',
|
||||
'RecommerceService',
|
||||
]
|
||||
40
myproject/integrations/services/marketplaces/base.py
Normal file
40
myproject/integrations/services/marketplaces/base.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from typing import Tuple
|
||||
import requests
|
||||
from ..base import BaseIntegrationService
|
||||
|
||||
|
||||
class MarketplaceService(BaseIntegrationService):
|
||||
"""
|
||||
Базовый сервис для маркетплейсов.
|
||||
Содержит общие методы для работы с API маркетплейсов.
|
||||
"""
|
||||
|
||||
def _get_headers(self) -> dict:
|
||||
"""Получить заголовки для API запросов"""
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'MyProject/1.0',
|
||||
}
|
||||
|
||||
def _make_request(self, url: str, method: str = 'GET', **kwargs) -> Tuple[bool, dict, str]:
|
||||
"""
|
||||
Сделать HTTP запрос к API.
|
||||
|
||||
Returns:
|
||||
tuple: (success, response_data, error_message)
|
||||
"""
|
||||
try:
|
||||
response = requests.request(method, url, headers=self._get_headers(), timeout=30, **kwargs)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
return True, response.json(), ''
|
||||
else:
|
||||
error_msg = f"HTTP {response.status_code}: {response.text}"
|
||||
return False, {}, error_msg
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return False, {}, 'Таймаут соединения'
|
||||
except requests.exceptions.ConnectionError:
|
||||
return False, {}, 'Ошибка соединения'
|
||||
except Exception as e:
|
||||
return False, {}, str(e)
|
||||
142
myproject/integrations/services/marketplaces/recommerce.py
Normal file
142
myproject/integrations/services/marketplaces/recommerce.py
Normal file
@@ -0,0 +1,142 @@
|
||||
from typing import Tuple
|
||||
from .base import MarketplaceService
|
||||
|
||||
|
||||
class RecommerceService(MarketplaceService):
|
||||
"""
|
||||
Сервис для работы с Recommerce API.
|
||||
Recommerce - агрегатор маркетплейсов (WB, Ozon, Яндекс и др.)
|
||||
"""
|
||||
|
||||
API_BASE_URL = "https://api.recommerce.ru" # Пример, нужно уточнить
|
||||
|
||||
def _get_headers(self) -> dict:
|
||||
"""Получить заголовки с токеном авторизации"""
|
||||
headers = super()._get_headers()
|
||||
if self.config.api_token:
|
||||
headers['Authorization'] = f'Bearer {self.config.api_token}'
|
||||
return headers
|
||||
|
||||
def _get_api_url(self, path: str) -> str:
|
||||
"""Получить полный URL для API endpoint"""
|
||||
base = self.config.api_endpoint or self.API_BASE_URL
|
||||
return f"{base.rstrip('/')}/{path.lstrip('/')}"
|
||||
|
||||
def test_connection(self) -> Tuple[bool, str]:
|
||||
"""
|
||||
Проверить соединение с Recommerce API.
|
||||
|
||||
Returns:
|
||||
tuple: (success, message)
|
||||
"""
|
||||
if not self.config.api_token:
|
||||
return False, 'Не указан API токен'
|
||||
|
||||
# Проверка соединения через endpoint информации о магазине
|
||||
if self.config.merchant_id:
|
||||
url = self._get_api_url(f'/merchants/{self.config.merchant_id}')
|
||||
else:
|
||||
url = self._get_api_url('/merchants/me')
|
||||
|
||||
success, data, error = self._make_request(url)
|
||||
|
||||
if success:
|
||||
merchant_name = data.get('name', 'Магазин')
|
||||
return True, f'Соединение установлено: {merchant_name}'
|
||||
else:
|
||||
return False, f'Ошибка соединения: {error}'
|
||||
|
||||
def sync(self) -> Tuple[bool, str]:
|
||||
"""
|
||||
Выполнить синхронизацию с Recommerce.
|
||||
|
||||
Returns:
|
||||
tuple: (success, message)
|
||||
"""
|
||||
if not self.is_available():
|
||||
return False, 'Интеграция не настроена или отключена'
|
||||
|
||||
# TODO: реализовать полную синхронизацию
|
||||
# - Загрузка товаров с маркетплейсов
|
||||
# - Обновление цен
|
||||
# - Обновление остатков
|
||||
# - Загрузка заказов
|
||||
|
||||
return True, 'Синхронизация запущена (заглушка)'
|
||||
|
||||
def fetch_products(self) -> Tuple[bool, list, str]:
|
||||
"""
|
||||
Получить товары с Recommerce.
|
||||
|
||||
Returns:
|
||||
tuple: (success, products, error_message)
|
||||
"""
|
||||
url = self._get_api_url('/products')
|
||||
success, data, error = self._make_request(url)
|
||||
|
||||
if success:
|
||||
products = data.get('items', [])
|
||||
return True, products, ''
|
||||
else:
|
||||
return False, [], error
|
||||
|
||||
def push_product(self, product_data: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
Отправить товар на Recommerce.
|
||||
|
||||
Args:
|
||||
product_data: Данные товара для отправки
|
||||
|
||||
Returns:
|
||||
tuple: (success, message)
|
||||
"""
|
||||
url = self._get_api_url('/products')
|
||||
success, data, error = self._make_request(url, method='POST', json=product_data)
|
||||
|
||||
if success:
|
||||
product_id = data.get('id', '')
|
||||
return True, f'Товар отправлен: ID={product_id}'
|
||||
else:
|
||||
return False, f'Ошибка отправки: {error}'
|
||||
|
||||
def update_stock(self, product_id: str, quantity: int) -> Tuple[bool, str]:
|
||||
"""
|
||||
Обновить остаток товара.
|
||||
|
||||
Args:
|
||||
product_id: ID товара
|
||||
quantity: Количество
|
||||
|
||||
Returns:
|
||||
tuple: (success, message)
|
||||
"""
|
||||
url = self._get_api_url(f'/products/{product_id}/stock')
|
||||
success, data, error = self._make_request(
|
||||
url, method='PATCH', json={'quantity': quantity}
|
||||
)
|
||||
|
||||
if success:
|
||||
return True, f'Остаток обновлён: {quantity} шт.'
|
||||
else:
|
||||
return False, f'Ошибка обновления: {error}'
|
||||
|
||||
def update_price(self, product_id: str, price: float) -> Tuple[bool, str]:
|
||||
"""
|
||||
Обновить цену товара.
|
||||
|
||||
Args:
|
||||
product_id: ID товара
|
||||
price: Новая цена
|
||||
|
||||
Returns:
|
||||
tuple: (success, message)
|
||||
"""
|
||||
url = self._get_api_url(f'/products/{product_id}/price')
|
||||
success, data, error = self._make_request(
|
||||
url, method='PATCH', json={'price': price}
|
||||
)
|
||||
|
||||
if success:
|
||||
return True, f'Цена обновлена: {price} руб.'
|
||||
else:
|
||||
return False, f'Ошибка обновления: {error}'
|
||||
32
myproject/integrations/services/marketplaces/woocommerce.py
Normal file
32
myproject/integrations/services/marketplaces/woocommerce.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from typing import Tuple
|
||||
from .base import MarketplaceService
|
||||
|
||||
|
||||
class WooCommerceService(MarketplaceService):
|
||||
"""Сервис для работы с WooCommerce API"""
|
||||
|
||||
def test_connection(self) -> Tuple[bool, str]:
|
||||
"""Проверить соединение с WooCommerce API"""
|
||||
if not self.config.store_url:
|
||||
return False, 'Не указан URL магазина'
|
||||
|
||||
if not self.config.consumer_key or not self.config.consumer_secret:
|
||||
return False, 'Не указаны ключи API'
|
||||
|
||||
# TODO: реализовать проверку соединения с WooCommerce API
|
||||
return True, 'Соединение успешно (заглушка)'
|
||||
|
||||
def sync(self) -> Tuple[bool, str]:
|
||||
"""Выполнить синхронизацию с WooCommerce"""
|
||||
# TODO: реализовать синхронизацию
|
||||
return True, 'Синхронизация запущена (заглушка)'
|
||||
|
||||
def fetch_orders(self, limit: int = 50):
|
||||
"""Получить заказы с WooCommerce"""
|
||||
# TODO: реализовать
|
||||
pass
|
||||
|
||||
def push_products(self, products):
|
||||
"""Отправить товары на WooCommerce"""
|
||||
# TODO: реализовать
|
||||
pass
|
||||
Reference in New Issue
Block a user