Files
octopus/myproject/integrations/recommerce/services.py
Andrey Smakotin a5ab216934 feat(integrations): добавлена полная интеграция с Recommerce
Реализован клиент для работы с API Recommerce, включая:
- Клиент с методами для работы с товарами и заказами
- Сервисный слой для высокоуровневых операций
- Мапперы данных между форматами
- Обработку исключений

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 17:56:53 +03:00

105 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import List, Dict, Any, Optional
from django.conf import settings
from .client import RecommerceClient
from .mappers import to_api_product, from_api_order
from .exceptions import RecommerceError
# Imports for typing only to avoid circular dependency issues at module level if possible
# but for simplicity in this structure we'll import inside methods if needed or use 'Any'
class RecommerceService:
"""
Высокоуровневый сервис для интеграции с Recommerce.
Предоставляет методы для синхронизации товаров и заказов.
"""
def __init__(self, integration_instance):
"""
Args:
integration_instance: Экземпляр модели RecommerceIntegration
"""
self.integration = integration_instance
self.client = RecommerceClient(
store_url=integration_instance.store_url,
api_token=integration_instance.api_token
)
def update_product(self, product: Any, fields: Optional[List[str]] = None) -> bool:
"""
Обновить товар в Recommerce.
Args:
product: Экземпляр Product
fields: Список полей для обновления (например ['price', 'count']).
Если None - обновляются все поля.
Returns:
bool: Успех операции
"""
# Получаем остаток, если нужно
stock_count = None
if fields is None or 'count' in fields:
# Пытаемся получить остаток.
# Логика получения остатка может зависеть от вашей системы inventory.
# Здесь предполагаем, что у product есть метод или связь для получения общего остатка.
# Для простоты используем первый попавшийся Stock или 0
# В реальном проекте тут должна быть логика выбора склада
stock = product.stocks.first()
stock_count = int(stock.quantity_free) if stock else 0
data = to_api_product(product, stock_count=stock_count, fields=fields)
try:
# Сначала пробуем обновить
# SKU берем из data или продукта
sku = data.get('sku', getattr(product, 'sku', str(product.id)))
# Recommerce API: POST /catalog/products/{sku} для обновления
self.client.update_product(sku, data)
return True
except RecommerceError as e:
# Если 404 - товар не найден, можно попробовать создать?
# В рамках "простой" интеграции - пока просто логируем или рейзим
# Если нужно автоматическое создание:
# if isinstance(e, RecommerceAPIError) and e.status_code == 404:
# return self.create_product(product)
raise e
def create_product(self, product: Any) -> bool:
"""Создать товар в Recommerce"""
# Для создания нужны все поля
stock = product.stocks.first()
stock_count = int(stock.quantity_free) if stock else 0
data = to_api_product(product, stock_count=stock_count, fields=None)
self.client.create_product(data)
return True
def get_new_orders(self, updated_after: str) -> List[Dict[str, Any]]:
"""
Получить список новых заказов из Recommerce.
Args:
updated_after: Дата в формате 'Y-m-d-H-i-s'
Returns:
List[Dict]: Список DTO заказов (готовых для сохранения)
"""
raw_orders = self.client.get_orders(updated_after=updated_after)
orders_dto = []
for raw_order in raw_orders:
dto = from_api_order(raw_order)
orders_dto.append(dto)
return orders_dto
def check_connection(self) -> bool:
"""Проверить соединение"""
try:
# Пробуем получить список заказов (легкий запрос)
self.client.get_orders()
return True
except RecommerceError:
return False